/ content / docs / guides / 07_reactjs_relay.md
07_reactjs_relay.md
  1  ---
  2  title: Receive and Send Messages Using Waku Relay With ReactJS
  3  date: 2021-12-09T14:00:00+01:00
  4  weight: 7
  5  ---
  6  
  7  # Receive and Send Messages Using Waku Relay With ReactJS
  8  
  9  It is easy to use Waku Connect with ReactJS.
 10  In this guide, we will demonstrate how your ReactJS dApp can use Waku Relay to send and receive messages.
 11  
 12  Before starting, you need to choose a _Content Topic_ for your dApp.
 13  Check out the [how to choose a content topic guide](/docs/guides/01_choose_content_topic/) to learn more about content topics.
 14  For this guide, we are using a single content topic: `/min-react-js-chat/1/chat/proto`.
 15  
 16  # Setup
 17  
 18  Create a new React app:
 19  
 20  ```shell
 21  npx create-react-app relay-reactjs-chat
 22  cd relay-reactjs-chat
 23  ```
 24  
 25  ## `BigInt`
 26  
 27  Some of js-waku's dependencies use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
 28  that is [only supported by modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility).
 29  
 30  To ensure that `react-scripts` properly transpile your webapp code, update the `package.json` file:
 31  
 32  ```json
 33  {
 34    "browserslist": {
 35      "production": [
 36        ">0.2%",
 37        "not ie <= 99",
 38        "not android <= 4.4.4",
 39        "not dead",
 40        "not op_mini all"
 41      ]
 42    }
 43  }
 44  ```
 45  
 46  ## Setup polyfills
 47  
 48  A number of Web3 dependencies need polyfills.
 49  Said polyfills must be explicitly declared when using webpack 5.
 50  
 51  The latest `react-scripts` version uses webpack 5.
 52  
 53  We will describe below a method to configure polyfills when using `create-react-app`/`react-scripts` or webpack 5.
 54  This may not be necessary if you do not use `react-scripts` or if you use webpack 4.
 55  
 56  Start by installing the polyfill libraries:
 57  
 58  ```shell
 59  npm install --save assert buffer crypto-browserify process stream-browserify
 60  ```
 61  
 62  ### Webpack 5
 63  
 64  If you directly use webpack 5,
 65  then you can inspire yourself from this [webpack.config.js](https://github.com/status-im/wakuconnect-vote-poll-sdk/blob/main/examples/mainnet-poll/webpack.config.js).
 66  
 67  ### cra-webpack-rewired
 68  
 69  An alternative is to let `react-scripts` control the webpack 5 config and only override some elements using `cra-webpack-rewired`.
 70  
 71  Install `cra-webpack-rewired`:
 72  
 73  ```shell
 74  npm install -D cra-webpack-rewired
 75  ```
 76  
 77  Create a `config/webpack.extend.js` file at the root of your app:
 78  
 79  ```js
 80  const webpack = require("webpack");
 81  
 82  module.exports = {
 83    dev: (config) => {
 84      // Override webpack 5 config from react-scripts to load polyfills
 85      if (!config.resolve) config.resolve = {};
 86      if (!config.resolve.fallback) config.resolve.fallback = {};
 87      Object.assign(config.resolve.fallback, {
 88        buffer: require.resolve("buffer"),
 89        crypto: require.resolve("crypto-browserify"),
 90        stream: require.resolve("stream-browserify"),
 91      });
 92  
 93      if (!config.plugins) config.plugins = [];
 94      config.plugins.push(
 95        new webpack.DefinePlugin({
 96          "process.env.ENV": JSON.stringify("dev"),
 97        })
 98      );
 99      config.plugins.push(
100        new webpack.ProvidePlugin({
101          process: "process/browser.js",
102          Buffer: ["buffer", "Buffer"],
103        })
104      );
105  
106      if (!config.ignoreWarnings) config.ignoreWarnings = [];
107      config.ignoreWarnings.push(/Failed to parse source map/);
108  
109      return config;
110    },
111    prod: (config) => {
112      // Override webpack 5 config from react-scripts to load polyfills
113      if (!config.resolve) config.resolve = {};
114      if (!config.resolve.fallback) config.resolve.fallback = {};
115      Object.assign(config.resolve.fallback, {
116        buffer: require.resolve("buffer"),
117        crypto: require.resolve("crypto-browserify"),
118        stream: require.resolve("stream-browserify"),
119      });
120  
121      if (!config.plugins) config.plugins = [];
122      config.plugins.push(
123        new webpack.DefinePlugin({
124          "process.env.ENV": JSON.stringify("prod"),
125        })
126      );
127      config.plugins.push(
128        new webpack.ProvidePlugin({
129          process: "process/browser.js",
130          Buffer: ["buffer", "Buffer"],
131        })
132      );
133  
134      if (!config.ignoreWarnings) config.ignoreWarnings = [];
135      config.ignoreWarnings.push(/Failed to parse source map/);
136  
137      return config;
138    },
139  };
140  ```
141  
142  Use `cra-webpack-rewired` in the `package.json`, instead of `react-scripts`:
143  
144  ```
145     "scripts": {
146  -    "start": "react-scripts start",
147  -    "build": "react-scripts build",
148  -    "test": "react-scripts test",
149  -    "eject": "react-scripts eject"
150  +    "start": "cra-webpack-rewired start",
151  +    "build": "cra-webpack-rewired build",
152  +    "test": "cra-webpack-rewired test",
153  +    "eject": "cra-webpack-rewired eject"
154     },
155  ```
156  
157  Then, install [js-waku](https://npmjs.com/package/js-waku):
158  
159  ```shell
160  npm install --save js-waku
161  ```
162  
163  Start the dev server and open the dApp in your browser:
164  
165  ```shell
166  npm run start
167  ```
168  
169  # Create Waku Instance
170  
171  In order to interact with the Waku network, you first need a Waku instance.
172  Go to `App.js` and modify the `App` function:
173  
174  ```js
175  import { Waku } from "js-waku";
176  import * as React from "react";
177  
178  function App() {
179    const [waku, setWaku] = React.useState(undefined);
180    const [wakuStatus, setWakuStatus] = React.useState("None");
181  
182    // Start Waku
183    React.useEffect(() => {
184      // If Waku is already assigned, the job is done
185      if (!!waku) return;
186      // If Waku status not None, it means we are already starting Waku
187      if (wakuStatus !== "None") return;
188  
189      setWakuStatus("Starting");
190  
191      // Create Waku
192      Waku.create({ bootstrap: { default: true } }).then((waku) => {
193        // Once done, put it in the state
194        setWaku(waku);
195        // And update the status
196        setWakuStatus("Started");
197      });
198    }, [waku, wakuStatus]);
199  
200    return (
201      <div className="App">
202        <header className="App-header">
203          <p>Waku node's status: {wakuStatus}</p>
204        </header>
205      </div>
206    );
207  }
208  
209  export default App;
210  ```
211  
212  # Wait to be connected
213  
214  When using the `bootstrap` option, it may take some time to connect to other peers.
215  To ensure that you have relay peers available to send and receive messages,
216  use the `Waku.waitForRemotePeer()` async function:
217  
218  ```js
219  React.useEffect(() => {
220    if (!!waku) return;
221    if (wakuStatus !== "None") return;
222  
223    setWakuStatus("Starting");
224  
225    Waku.create({ bootstrap: { default: true } }).then((waku) => {
226      setWaku(waku);
227      setWakuStatus("Connecting");
228      waku.waitForRemotePeer().then(() => {
229        setWakuStatus("Ready");
230      });
231    });
232  }, [waku, wakuStatus]);
233  ```
234  
235  # Define Message Format
236  
237  To define the Protobuf message format,
238  you can use [protobufjs](https://www.npmjs.com/package/protobufjs):
239  
240  ```shell
241  npm install protobufjs
242  ```
243  
244  Define `SimpleChatMessage` with two fields: `timestamp` and `text`.
245  
246  ```js
247  import protobuf from "protobufjs";
248  
249  const SimpleChatMessage = new protobuf.Type("SimpleChatMessage")
250      .add(new protobuf.Field("timestamp", 1, "uint64"))
251      .add(new protobuf.Field("text", 2, "string"));
252  ```
253  
254  # Send Messages
255  
256  Create a function that takes the Waku instance and a message to send:
257  
258  ```js
259  import {WakuMessage} from "js-waku";
260  
261  const ContentTopic = `/relay-reactjs-chat/1/chat/proto`;
262  
263  function sendMessage(message, waku, timestamp) {
264      const time = timestamp.getTime();
265  
266      // Encode to protobuf
267      const protoMsg = SimpleChatMessage.create({
268          timestamp: time,
269          text: message,
270      });
271      const payload = SimpleChatMessage.encode(protoMsg).finish();
272  
273      // Wrap in a Waku Message
274      return WakuMessage.fromBytes(payload, ContentTopic).then((wakuMessage) =>
275          // Send over Waku Relay
276          waku.relay.send(wakuMessage)
277      );
278  }
279  ```
280  
281  Then, add a button to the `App` function:
282  
283  ```js
284  function App() {
285    const [waku, setWaku] = React.useState(undefined);
286    const [wakuStatus, setWakuStatus] = React.useState("None");
287    // Using a counter just for the messages to be different
288    const [sendCounter, setSendCounter] = React.useState(0);
289  
290    React.useEffect(() => {
291      // ... creates Waku
292    }, [waku, wakuStatus]);
293  
294    const sendMessageOnClick = () => {
295      // Check Waku is started and connected first.
296      if (wakuStatus !== "Ready") return;
297  
298      sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() =>
299        console.log("Message sent")
300      );
301  
302      // For demonstration purposes.
303      setSendCounter(sendCounter + 1);
304    };
305  
306    return (
307      <div className="App">
308        <header className="App-header">
309          <p>{wakuStatus}</p>
310          <button onClick={sendMessageOnClick} disabled={wakuStatus !== "Ready"}>
311            Send Message
312          </button>
313        </header>
314      </div>
315    );
316  }
317  ```
318  
319  # Receive Messages
320  
321  To process incoming messages, you need to register an observer on Waku Relay.
322  First, you need to define the observer function.
323  
324  You will need to remove the observer when the component unmount.
325  Hence, you need the reference to the function to remain the same.
326  For that, use `React.useCallback`:
327  
328  ```js
329  const processIncomingMessage = React.useCallback((wakuMessage) => {
330      // Empty message?
331      if (!wakuMessage.payload) return;
332  
333      // Decode the protobuf payload
334      const {text, timestamp} = SimpleChatMessage.decode(wakuMessage.payload);
335  
336      const time = new Date();
337      time.setTime(timestamp);
338  
339      // For now, just log new messages on the console
340      console.log(`message received at ${time.toString()}: ${text}`);
341  }, []);
342  ```
343  
344  Then, add this observer to Waku Relay.
345  Do not forget to delete the observer is the component is being unmounted:
346  
347  ```js
348  React.useEffect(() => {
349    if (!waku) return;
350  
351    // Pass the content topic to only process messages related to your dApp
352    waku.relay.addObserver(processIncomingMessage, [ContentTopic]);
353  
354    // `cleanUp` is called when the component is unmounted, see ReactJS doc.
355    return function cleanUp() {
356      waku.relay.deleteObserver(processIncomingMessage, [ContentTopic]);
357    };
358  }, [waku, wakuStatus, processIncomingMessage]);
359  ```
360  
361  # Display Messages
362  
363  The Waku work is now done.
364  Your dApp is able to send and receive messages using Waku.
365  For the sake of completeness, let's display received messages on the page.
366  
367  First, add incoming messages to the state of the `App` component:
368  
369  ```js
370  function App() {
371      //...
372  
373      const [messages, setMessages] = React.useState([]);
374  
375      const processIncomingMessage = React.useCallback((wakuMessage) => {
376          if (!wakuMessage.payload) return;
377  
378          const {text, timestamp} = SimpleChatMessage.decode(wakuMessage.payload);
379  
380          const time = new Date();
381          time.setTime(timestamp);
382          const message = {text, timestamp: time};
383  
384          setMessages((messages) => {
385              return [message].concat(messages);
386          });
387      }, []);
388  
389      // ...
390  }
391  ```
392  
393  Then, render the messages:
394  
395  ```js
396  function App() {
397    // ...
398  
399    return (
400      <div className="App">
401        <header className="App-header">
402          <p>{wakuStatus}</p>
403          <button onClick={sendMessageOnClick} disabled={wakuStatus !== "Ready"}>
404            Send Message
405          </button>
406          <ul>
407            {messages.map((msg) => {
408              return (
409                <li>
410                  <p>
411                    {msg.timestamp.toString()}: {msg.text}
412                  </p>
413                </li>
414              );
415            })}
416          </ul>
417        </header>
418      </div>
419    );
420  }
421  ```
422  
423  And VoilĂ ! You should now be able to send and receive messages.
424  Try out by opening the app from different browsers.
425  
426  You can see the complete code in the [Relay ReactJS Chat Example App](https://github.com/status-im/js-waku/tree/main/examples/relay-reactjs-chat).