Jump to content
RAGE Multiplayer Community

[Guide] Creating UI with React


Mispon
 Share

Recommended Posts

Good day to all!

I recently started developing my server and decided to try doing all UI on React. Today I want to share how to set up a project for convenient work with this framework!

I loaded the finished example on github: https://github.com/Mispon/rage-mp-react-ui

There are also collected files in the archive server-files.rar for quick test.

And now in order:

  1. First you need to initialize the project by running the command npm init
  2. Then install all the necessary packages. That is my packege.json file
    {
      "name": "rage-mp-react-ui",
      "version": "1.0.0",
      "description": "Example repo to explain how use react in rage mp servers",
      "main": "index.js",
      "scripts": {
        "start": "npm run dev",
        "dev": "set NODE_ENV=development && webpack",
        "prod": "set NODE_ENV=production && webpack"
      },
      "repository": {
        "type": "git",
        "url": "git+https://github.com/Mispon/rage-mp-react-ui.git"
      },
      "keywords": [
        "rage-mp",
        "react",
        "rage",
        "mispon",
        "gta5",
        "gtav"
      ],
      "author": "Mispon",
      "license": "ISC",
      "bugs": {
        "url": "https://github.com/Mispon/rage-mp-react-ui/issues"
      },
      "homepage": "https://github.com/Mispon/rage-mp-react-ui#readme",
      "devDependencies": {
        "babel-core": "^6.26.3",
        "babel-loader": "^7.1.5",
        "babel-preset-es2015": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "css-loader": "^1.0.0",
        "node-sass": "^4.9.3",
        "sass-loader": "^7.1.0",
        "style-loader": "^0.23.0",
        "webpack": "^4.20.2",
        "webpack-cli": "^3.1.2"
      },
      "dependencies": {
        "react": "^16.5.2",
        "react-dom": "^16.5.2"
      }
    }
    You can do it by command npm i packetname --save (or --save-dev if packet will use only in developing, like babel-core and etc).
  3. Next you need to create webpack.config.js and paste the following settings into it:
    const webpack = require('webpack')
    const path = require('path')
    
    let NODE_ENV = 'production'
    if (process.env.NODE_ENV) {
        NODE_ENV = process.env.NODE_ENV.replace(/^\s+|\s+$/g, "")
    }
    
    module.exports = {
        entry: {
            index: './index.js',
            app: './ui/app/app.js'
        },
        output: {
            path: path.resolve(__dirname, 'build'),
    		filename: '[name].js'
        },
        watch: NODE_ENV == 'development',
        module: {
            rules: [{
                    test: /\.js$/,
                    exclude: [/node_modules/],
                    loader: 'babel-loader',
                    query: {
                        presets: ['react', 'es2015']
                    }
                },
                {
                    test: /\.scss$/,
                    use: ["style-loader", "css-loader", "sass-loader"]
                }
            ]
        },
        resolve: {
            extensions: ['.js', '.json']
        },
    	optimization: {
    		minimize: true
    	},
        mode: NODE_ENV,
        plugins: [
            new webpack.EnvironmentPlugin('NODE_ENV')
        ]
    }
    I will not go into details, there is documentation for this. In short, the webpack will collect all the files of our application into one (or several) ready-made files (and styles too!).
  4. Then create clients index.js file, a separate folder for the UI with single index.html, app.js, bridge.js and some style file. In my case I use .scss. After you got something like that:
  5.           bGVIo4_K15w.jpg
  6. In index.js add simple code for test how events works:
    let browser
    
    mp.events.add('guiReady', () => {
        browser = mp.browsers.new('package://ui/index.html')
    })
    
    // Handle event from server and send data to react app
    mp.events.add('onMessageFromServer', (value) => {
        browser.execute(`trigger('onMessage', '${value}')`)
    })
    
    // Handle event from react app
    mp.events.add('showUrl', (url) => {
        mp.gui.chat.push(url)
    })
    
    // F12 - trigger cursor
    mp.keys.bind(0x7B, true, () => {
        let state = !mp.gui.cursor.visible
        mp.gui.cursor.show(state, state)
    })

     

  7. In app.js we'll add our example react component and render it in index.html:

    import '../styles/app.scss'
    
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                time: new Date(),
                message: 'default message'
            }
        }
    
        componentDidMount() {
            EventManager.addHandler('onMessage', this.onMessage.bind(this))
            this.timerId = setInterval(() => {
                this.setState({time: new Date()})
            }, 1000)
        }
    
        componentWillUnmount() {
            clearInterval(this.timerId)
            EventManager.removeHandler('onMessage', this.onMessage)
        }
    
        onMessage(value) {
            this.setState({message: value})
        }
    
        // send current url to client
        click() {
            let currentUrl = window.location.pathname        
            mp.trigger('showUrl', currentUrl)
        }
    
        render() {
            return(
                <div className="app">
                    <h1>Make UI on React!</h1>
                    <p className="current-time">{this.state.time.toLocaleTimeString()}</p>
                    <p className="message">{this.state.message}</p>
                    <button className="send-button" onClick={this.click}>Send</button>
                </div>
            )
        }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))

    Here everything is as in normal work with the react. At the very beginning we connect our styles. You can define them to your taste. Next, create the component, define the default state, write the HTML code that will be rendered in the browser, and add the necessary handlers in the component. But there is one very important point! In the componentDidMount method, using the EventManager which is global space, we subscribe to the event that will be passed from the client to our react-application.  At its core, EventManager is a bridge between client multiplayer code and our UI.

  8. Let's define EventManager in our bridge.js:

    // Global variable for describe on events from react components
    var EventManager = {
        events: {},
        
        addHandler: function(eventName, handler) {
            if (eventName in this.events) {
                this.events[eventName].push(handler);
            }
            else {
                this.events[eventName] = [handler];
            }
        },
            
        removeHandler: function(eventName, handler) { 
            if (eventName in this.events) {
                var index = this.events[eventName].indexOf(handler);
                this.events[eventName].splice(index, 1);
            }
        }
    }
    
    // Handle events from client
    function trigger(eventName, args) {
        var handlers = EventManager.events[eventName];
        handlers.forEach(handler => handler(JSON.parse(args)));
    }

    The trigger function serves as the only point of access to our UI from the client and delegates execution to the accessible handler by the name of the event. That is, in any component we can create a handler, subscribe to an event interesting to us and update its state depending on what happens in the game. In client code we will only use browser.execute(`trigger('eventName')`).

  9.   In addition as simple as possible index.html and my app.scss:

    <html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1">
        
        <!-- Don't forget to change paths after copy in server packages -->
        <script src="app/bridge.js" defer></script>
        <script src="../build/app.js" defer></script>
    </head>
    <body>
        <div id="root"></div>
    </body>
    </html>
    .app {
        h1 {
            text-align: center;
            font-weight: bold;
            padding-top: 2vh;
        }
        .message {       
            text-align: center;
            font-size: 2vw;
            padding-top: 2vh;
        }
        .current-time {
            display: block;
            position: absolute;
            right: 1vw;
            bottom: 0;
        }
        .send-button {
            display: block;
            width: 100px;
            height: 50px;
            margin: 15% auto;
            outline: none;
        }
        position: relative;
        width: 30%;
        height: 40vh;
        margin: 20vh auto;
        border-radius: 10px;
        box-shadow: 1px 1px 2px black;
        background-color: black;
        color: white;
    }

     

  10. Now you can run the npm start or npm run dev in the root directory to build the project.

Check full example usage on my github, link above.

 

Hope it's help for someone. Be good in developing!

Mispon (discord: Mispon#8498)

 

 

Edited by Mispon
correct typos
  • Like 7
  • Mask 1
Link to comment
Share on other sites

  • 1 year later...
  • 1 month later...
On 3/20/2020 at 12:12 PM, Sambler said:

Did everything as specified, error on startup
download.png

You have to install babel-loader and all other dependencies. Check step 2 devDependencies and dependencies. Make sure to have the package.json with the same content and then type npm install.

Link to comment
Share on other sites

  • 2 months later...
  • 8 months later...
В 29.07.2020 в 22:48, Mrage сказал:

Better way is to:

npm i -g create-react-app
create-react-app projectname

Than doin strange things for ez projects lol

Better better:

npx create-react-app (project-name)

 

Link to comment
Share on other sites

  • 2 weeks later...
  • 2 months later...

Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
 - configuration.module.rules[0] has an unknown property 'query'. These properties are valid:
   object { compiler?, dependency?, descriptionData?, enforce?, exclude?, generator?, include?, issuer?, issuerLayer?, layer?, loader?, mimetype?, oneOf?, options?, parser?, realResource?, resolve?, resource?, resourceFragment?, resourceQuery?, rules?, scheme?, sideEffects?, test?, type?, use? }
   -> A rule description with conditions and effects for modules.

How to fix this?

here is my code

const webpack = require('webpack')
const path = require('path')

let NODE_ENV = 'production'
if (process.env.NODE_ENV) {
    NODE_ENV = process.env.NODE_ENV.replace(/^\s+|\s+$/g, "")
}

module.exports = {
    entry: './src/App.js',
    output: {
        path: path.resolve(__dirname, 'src/buildInterfaces'),
        filename: '[name].js'
    },
    module: {
        rules: [{
                test: /\.js$/,
                exclude: [/node_modules/],
                loader: 'babel-loader',
                query: {
                    presets: ['react', 'es2015']
                }
            },
        ]
    },
    resolve: {
        extensions: ['.js', '.json']
    },
    optimization: {
        minimize: false
    },
    mode: NODE_ENV,
    plugins: [
        new webpack.EnvironmentPlugin('NODE_ENV')
    ]
}

Link to comment
Share on other sites

  • 2 weeks later...
On 7/18/2021 at 6:46 PM, ilhmjv said:

Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
 - configuration.module.rules[0] has an unknown property 'query'. These properties are valid:
   object { compiler?, dependency?, descriptionData?, enforce?, exclude?, generator?, include?, issuer?, issuerLayer?, layer?, loader?, mimetype?, oneOf?, options?, parser?, realResource?, resolve?, resource?, resourceFragment?, resourceQuery?, rules?, scheme?, sideEffects?, test?, type?, use? }
   -> A rule description with conditions and effects for modules.

How to fix this?

here is my code

const webpack = require('webpack')
const path = require('path')

let NODE_ENV = 'production'
if (process.env.NODE_ENV) {
    NODE_ENV = process.env.NODE_ENV.replace(/^\s+|\s+$/g, "")
}

module.exports = {
    entry: './src/App.js',
    output: {
        path: path.resolve(__dirname, 'src/buildInterfaces'),
        filename: '[name].js'
    },
    module: {
        rules: [{
                test: /\.js$/,
                exclude: [/node_modules/],
                loader: 'babel-loader',
                query: {
                    presets: ['react', 'es2015']
                }
            },
        ]
    },
    resolve: {
        extensions: ['.js', '.json']
    },
    optimization: {
        minimize: false
    },
    mode: NODE_ENV,
    plugins: [
        new webpack.EnvironmentPlugin('NODE_ENV')
    ]
}

This one about webpack, new syntax :)

 

{
  test: /\.m?js$/,
  exclude: /(node_modules|bower_components)/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['react', 'es2015']
    }
  }
}

 

Edited by ch3rn1k
Link to comment
Share on other sites

  • 2 months later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...