Skip to content
Julian Knight edited this page Dec 21, 2022 · 23 revisions

Example: ReactJS

This is a quick introduction on how you can use Node-red, uibuilder and react all together.

NOTE 2022-12-21:

Please see both the updated version of this at github.com/gaillarddamien/uibuilder-react-example AND also the issues raised on Issue #2 on that repo (unless the repo has been updated).

Also see the ReactJS example with no build step required on this WIKI.

Installation & Configuration

NB: I will assume that you are using Linux and have installed Node-RED in the default way (globally). I also assume that your <userDir> folder is in the default location (~/.node-red) and that you are not using projects. Adjust the example paths if some of that isn't true.

  • Install uibuilder from the Node-RED palette menu.
  • Change the name and URL in the resulting node to something helpful to you - this example will use the URL react_ui.
  • Under "Advanced Settings", turn off "Copy index ...." because we won't be using those files.
  • Deploy.
  • Use uibuilder's library manager to install reactjs.

Unfortunately, REACT is too complex to allow everything to be done from within the Node-RED Editor. So from here, you need a terminal session on your server so that you can run command lines.

cd ~/.node-red/uibuilder
# create a new react app, using the default template (see https://github.com/facebook/create-react-app for details)
npx create-react-app react_ui
cd react_ui
# install node-red-contrib-uibuilder as a local/relative dev dependency
npm install ../../node_modules/node-red-contrib-uibuilder --save-dev

~/.node-red/uibuilder/react_ui/.env

# see https://create-react-app.dev/docs/advanced-configuration/ for details
PUBLIC_URL=.
BUILD_PATH=./dist
BROWSER=none

~/.node-red/uibuilder/react_ui/public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

    <title>React UI - uibuilder and Node-RED</title>
</head>
<body>

<div>
    <h1>
        Welcome to REACT-UIbuilder for Node-RED!
    </h1>
</div>
<div id="root">
    <div id="app">
    </div>
</div>

</body>
</html>

~/.node-red/uibuilder/react_ui/src/App.js

import React from 'react';
import './App.css';

// Import uibuilder enabled component
import UserData from './scenes/UserData';

function App() {
    return (
        <div className="App">

            {/* THIS IS THE UIBUILDER COMPONENT */}
            <UserData title="User Data"></UserData>

        </div>
    );
}

export default App;

~/.node-red/uibuilder/react_ui/src/App.css

body {
    margin: 1rem;
    padding: 1rem;
}

.d1 {
  margin: 0.5rem;
  padding: 0.5rem;
}

~/.node-red/uibuilder/react_ui/src/scenes/UserData.js

/* This is where the uibuilder action happens */
import React, {Component} from 'react';
//import ReactDOM from 'react-dom';
//import { findDOMNode } from 'react-dom';

import uibuilder from 'node-red-contrib-uibuilder/front-end/uibuilderfe'

class UserData extends Component {
    constructor(props) {
        super(props)

        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names
         */
        uibuilder.start();

        this.state = {
            // Example of retrieving data from uibuilder
            feVersion: uibuilder.get('version'),

            socketConnectedState: false,
            serverTimeOffset: '[unknown]',

            msgRecvd: '[Nothing]',
            msgsReceived: 0,
            msgCtrl: '[Nothing]',
            msgsControl: 0,

            msgSent: '[Nothing]',
            msgsSent: 0,
            msgCtrlSent: '[Nothing]',
            msgsCtrlSent: 0,
        }

        /** You can use the following to help trace how messages flow back and forth.
         * You can then amend this processing to suite your requirements.
         */

        //#region ---- Trace Received Messages ---- //
        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
        // newVal relates to the attribute being listened to.
        uibuilder.onChange('msg', (newVal) => {

            this.setState({'msgRecvd': newVal});

            console.info('[uibuilder.onChange] msg received from Node-RED server:', newVal);
        })

        // As we receive new messages, we get an updated count as well
        uibuilder.onChange('msgsReceived', (newVal) => {
            console.info('[uibuilder.onChange] Updated count of received msgs:', newVal);

            this.setState({'msgsReceived': newVal});
        })

        // If we receive a control message from Node-RED, we can get the new data here - we pass it to a Vue variable
        uibuilder.onChange('ctrlMsg', (newVal) => {
            console.info('[uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', newVal);

            this.setState({'msgCtrl': newVal});
        })
        // Updated count of control messages received
        uibuilder.onChange('msgsCtrl', (newVal) => {
            console.info('[uibuilder.onChange:msgsCtrl] Updated count of received CONTROL msgs:', newVal);

            this.setState({'msgsControl': newVal});
        })
        //#endregion ---- End of Trace Received Messages ---- //

        //#region ---- Trace Sent Messages ---- //
        // You probably only need these to help you understand the order of processing //
        // If a message is sent back to Node-RED, we can grab a copy here if we want to
        uibuilder.onChange('sentMsg', (newVal) => {
            console.info('[uibuilder.onChange:sentMsg] msg sent to Node-RED server:', newVal);

            this.setState({'msgSent': newVal});
        })
        // Updated count of sent messages
        uibuilder.onChange('msgsSent', (newVal) => {
            console.info('[uibuilder.onChange:msgsSent] Updated count of msgs sent:', newVal);

            this.setState({'msgsSent': newVal});
        })

        // If we send a control message to Node-RED, we can get a copy of it here
        uibuilder.onChange('sentCtrlMsg', (newVal) => {
            console.info('[uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', newVal);

            this.setState({'msgCtrlSent': newVal});
        })
        // And we can get an updated count
        uibuilder.onChange('msgsSentCtrl', (newVal) => {
            console.info('[uibuilder.onChange:msgsSentCtrl] Updated count of CONTROL msgs sent:', newVal);

            this.setState({'msgsCtrlSent': newVal});
        })
        //#endregion ---- End of Trace Sent Messages ---- //

        // If Socket.IO connects/disconnects, we get true/false here
        uibuilder.onChange('ioConnected', (newVal) => {
            console.info('[uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', newVal)

            this.setState({'socketConnectedState': newVal})
        })
        // If Server Time Offset changes
        uibuilder.onChange('serverTimeOffset', (newVal) => {
            console.info('[uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', newVal)

            this.setState({'serverTimeOffset': newVal})
        })

        //Manually send a message back to Node-RED after 2 seconds
        window.setTimeout(function () {
            console.info('Sending a message back to Node-RED-after2sdelay')
            uibuilder.send({'topic': 'uibuilderfe', 'payload': 'I am a message sent from the uibuilder front end'})
        }, 2000)
    }


    render() {
        return (

            <div ref="root" style={{height: "50vh"}}>
                <hr></hr>
                <div className="d1">
                    <div>Last msg Received:</div>
                    <pre><code>{JSON.stringify(this.state.msgRecvd, null, 2)}</code></pre>
                    <div># Msgs Received: {this.state.msgsReceived}</div>
                </div>

                <div className="d1">
                    <div>last Ctl Msg Received:</div>
                    <pre><code>{JSON.stringify(this.state.msgCtrl, null, 2)}</code></pre>
                    <div># Control Msgs Received: {this.state.msgsControl}</div>
                </div>

                <div className="d1">
                    <div>last Msg Sent</div>
                    <pre><code>{JSON.stringify(this.state.msgSent, null, 2)}</code></pre>
                    <div># msgs Sent: {this.state.msgsSent}</div>
                </div>

                <div className="d1">
                    <div>Socket Connected?: {this.state.socketConnectedState}</div>
                    <div>uibuilderfe Version: {this.state.feVersion}</div>
                    <div>Server Time Offset from browser: {this.state.serverTimeOffset}</div>
                </div>

            </div>
        );

    }

}

export default UserData

build

npm run build

This will produce a production build under ./dist. You can now browse http://localhost:1880/react_ui

dev server

~/.node-red/uibuilder/react_ui/package.json

Add the following line

{
	...
	"proxy": "http://localhost:1880"
	...
}
npm run start

App is now available at http://localhost:3000/react_ui


NOTE: This example was built on top of the former version of this wiki page, and I assumed it should remain as close as possible from the original submission. I have also produced a FP (functional programming) version of it, as well as a TypeScript version. Please see branches of https://github.com/gaillarddamien/uibuilder-react-example if you're interested.

Clone this wiki locally