mirror of https://github.com/waku-org/js-waku.git
Add ReactJS Relay guide
This commit is contained in:
parent
f95e52c2ea
commit
943b1c2456
|
@ -15,9 +15,8 @@ message SimpleChatMessage {
|
|||
function App() {
|
||||
const [waku, setWaku] = React.useState(undefined);
|
||||
const [wakuStatus, setWakuStatus] = React.useState('None');
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
const [currentTime, setCurrentTime] = React.useState(new Date());
|
||||
const [sendCounter, setSendCounter] = React.useState(0);
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!!waku) return;
|
||||
|
@ -61,19 +60,10 @@ function App() {
|
|||
};
|
||||
}, [waku, wakuStatus, processIncomingMessage]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [currentTime]);
|
||||
|
||||
const sendMessageOnClick = () => {
|
||||
if (wakuStatus !== 'Ready') return;
|
||||
|
||||
sendMessage(`Here is message #${sendCounter}`, waku, currentTime).then(() =>
|
||||
sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() =>
|
||||
console.log('Message sent')
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
# Receive and Send Messages Using Waku Relay With ReactJS
|
||||
|
||||
It is easy to use DappConnect with ReactJS.
|
||||
In this guide, we will demonstrate how your ReactJS dApp can use Waku Relay to send and receive messages.
|
||||
|
||||
Before starting, you need to choose a _Content Topic_ for your dApp.
|
||||
Check out the [how to choose a content topic guide](choose-content-topic.md) to learn more about content topics.
|
||||
For this guide, we are using a unique content topic: `/min-js-web-chat/1/chat/proto`.
|
||||
|
||||
# Setup
|
||||
|
||||
Create a new react app:
|
||||
|
||||
```shell
|
||||
npx create-react-app min-js-web-chat
|
||||
cd min-js-web-chat
|
||||
```
|
||||
|
||||
Then, install [js-waku](https://npmjs.com/package/js-waku):
|
||||
|
||||
```shell
|
||||
npm install js-waku
|
||||
```
|
||||
|
||||
Start the dev server and open the dApp in your browser:
|
||||
|
||||
```shell
|
||||
npm run start
|
||||
```
|
||||
|
||||
Note: We have noticed some issues with React bundling due to `npm` pulling an old version of babel.
|
||||
If you are getting an error about the [optional chaining (?.)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
|
||||
character not being valid, try cleaning up and re-installing your dependencies:
|
||||
|
||||
```shell
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
# Create Waku Instance
|
||||
|
||||
In order to interact with the Waku network, you first need a Waku instance.
|
||||
Go to `App.js` and modify the `App` function:
|
||||
|
||||
```js
|
||||
import { Waku } from 'js-waku';
|
||||
import * as React from 'react';
|
||||
|
||||
function App() {
|
||||
const [waku, setWaku] = React.useState(undefined);
|
||||
const [wakuStatus, setWakuStatus] = React.useState('None');
|
||||
|
||||
// Start Waku
|
||||
React.useEffect(() => {
|
||||
// If Waku is already assigned, the job is done
|
||||
if (!!waku) return;
|
||||
// If Waku status not None, it means we already started Waku
|
||||
if (wakuStatus !== 'None') return;
|
||||
|
||||
setWakuStatus('Starting');
|
||||
|
||||
// Create Waku
|
||||
Waku.create().then((waku) => {
|
||||
// Once done, put it in the state
|
||||
setWaku(waku);
|
||||
// And update the status
|
||||
setWakuStatus('Started');
|
||||
});
|
||||
}, [waku, wakuStatus]);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
// Display the status on the web page
|
||||
<p>{wakuStatus}</p>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# Connect to Other Peers
|
||||
|
||||
The Waku instance needs to connect to other peers to communicate with the network.
|
||||
First, create `bootstrapWaku` to connect to the Status fleet:
|
||||
|
||||
```js
|
||||
import { getStatusFleetNodes } from 'js-waku';
|
||||
|
||||
async function bootstrapWaku(waku) {
|
||||
// Retrieve node addresses from https://fleets.status.im/
|
||||
const nodes = await getStatusFleetNodes();
|
||||
// Connect to the nodes
|
||||
await Promise.all(nodes.map((addr) => waku.dial(addr)));
|
||||
}
|
||||
```
|
||||
|
||||
Then, bootstrap after Waku is created in the previous `useEffect` block:
|
||||
|
||||
```js
|
||||
React.useEffect(() => {
|
||||
if (!!waku) return;
|
||||
if (wakuStatus !== 'None') return;
|
||||
|
||||
setWakuStatus('Starting');
|
||||
|
||||
Waku.create().then((waku) => {
|
||||
setWaku(waku);
|
||||
setWakuStatus('Connecting');
|
||||
bootstrapWaku(waku).then(() => {
|
||||
setWakuStatus('Ready');
|
||||
});
|
||||
});
|
||||
}, [waku, wakuStatus]);
|
||||
```
|
||||
|
||||
DappConnect will provide more discovery and bootstrap methods over time, or you can make your own.
|
||||
|
||||
# Define Message Format
|
||||
|
||||
To define the Protobuf message format,
|
||||
use [protons](https://www.npmjs.com/package/protons)
|
||||
|
||||
```shell
|
||||
npm install protons
|
||||
```
|
||||
|
||||
Define `SimpleChatMessage` with two fields: `timestamp` and `text`.
|
||||
|
||||
```js
|
||||
import protons from 'protons';
|
||||
|
||||
const proto = protons(`
|
||||
message SimpleChatMessage {
|
||||
uint64 timestamp = 1;
|
||||
string text = 2;
|
||||
}
|
||||
`);
|
||||
```
|
||||
|
||||
# Send Messages
|
||||
|
||||
Create a function that takes the Waku instance and a message to send:
|
||||
|
||||
```js
|
||||
import { WakuMessage } from 'js-waku';
|
||||
|
||||
const ContentTopic = `/min-js-web-chat/1/chat/proto`;
|
||||
|
||||
async function sendMessage(message, waku, timestamp) {
|
||||
const time = timestamp.getTime();
|
||||
|
||||
// Encode to protobuf
|
||||
const payload = proto.SimpleChatMessage.encode({
|
||||
timestamp: time,
|
||||
text: message,
|
||||
});
|
||||
|
||||
// Wrap in a Waku Message
|
||||
const wakuMessage = await WakuMessage.fromBytes(payload, ContentTopic);
|
||||
|
||||
// Send over Waku Relay
|
||||
await waku.relay.send(wakuMessage);
|
||||
}
|
||||
```
|
||||
|
||||
Then, add a button to send messages to the `App` function:
|
||||
|
||||
```js
|
||||
function App() {
|
||||
const [waku, setWaku] = React.useState(undefined);
|
||||
const [wakuStatus, setWakuStatus] = React.useState('None');
|
||||
// Using a counter just for the messages to be different
|
||||
const [sendCounter, setSendCounter] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
// ... creates Waku
|
||||
}, [waku, wakuStatus]);
|
||||
|
||||
const sendMessageOnClick = () => {
|
||||
if (wakuStatus !== 'Ready') return;
|
||||
|
||||
sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() =>
|
||||
console.log('Message sent')
|
||||
);
|
||||
|
||||
setSendCounter(sendCounter + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<p>{wakuStatus}</p>
|
||||
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}>
|
||||
Send Message
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# Receive Messages
|
||||
|
||||
To process incoming messages, you need to register an observer on Waku Relay.
|
||||
First, you need to define the function that acts as observer.
|
||||
|
||||
As you are passing the function to Waku Relay, it is best to use `React.useCallback` so that the reference to the function remains the same:
|
||||
|
||||
```js
|
||||
const processIncomingMessage = React.useCallback((wakuMessage) => {
|
||||
// Empty message?
|
||||
if (!wakuMessage.payload) return;
|
||||
|
||||
// Decode the protobuf payload
|
||||
const { timestamp, text } = proto.SimpleChatMessage.decode(
|
||||
wakuMessage.payload
|
||||
);
|
||||
const time = new Date();
|
||||
time.setTime(timestamp);
|
||||
|
||||
// For now, just log new messages on the console
|
||||
console.log(`message received at ${time.toString()}: ${text}`);
|
||||
}, []);
|
||||
```
|
||||
|
||||
Then, add this function as an observer to Waku Relay.
|
||||
Do not forget to delete the observer is the component is being unmounted:
|
||||
|
||||
```js
|
||||
React.useEffect(() => {
|
||||
if (!waku) return;
|
||||
|
||||
// Pass the content topic to only process messages related to your dApp
|
||||
waku.relay.addObserver(processIncomingMessage, [ContentTopic]);
|
||||
|
||||
// `cleanUp` is called when the component is unmounted, see ReactJS doc.
|
||||
return function cleanUp() {
|
||||
waku.relay.deleteObserver(processIncomingMessage, [ContentTopic]);
|
||||
};
|
||||
}, [waku, wakuStatus, processIncomingMessage]);
|
||||
```
|
||||
|
||||
# Display Messages
|
||||
|
||||
The Waku work is now done.
|
||||
Your dApp is able to send and receive messages using Waku.
|
||||
For the sake of completeness, let's display received messages on the page.
|
||||
|
||||
First, modify the `App()` function to store incoming messages:
|
||||
|
||||
```js
|
||||
function App() {
|
||||
//...
|
||||
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
|
||||
const processIncomingMessage = React.useCallback((wakuMessage) => {
|
||||
if (!wakuMessage.payload) return;
|
||||
|
||||
const { text, timestamp } = proto.SimpleChatMessage.decode(
|
||||
wakuMessage.payload
|
||||
);
|
||||
|
||||
const time = new Date();
|
||||
time.setTime(timestamp);
|
||||
const message = { text, timestamp: time };
|
||||
|
||||
setMessages((currMessages) => {
|
||||
return [message].concat(currMessages);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Then, add the messages to the rendering :
|
||||
|
||||
```js
|
||||
function App() {
|
||||
// ...
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<p>{wakuStatus}</p>
|
||||
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}>
|
||||
Send Message
|
||||
</button>
|
||||
<ul>
|
||||
{messages.map((msg) => {
|
||||
return (
|
||||
<li>
|
||||
<p>
|
||||
{msg.timestamp.toString()}: {msg.text}
|
||||
</p>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
And Voilà! You should now be able to send and receive messages from two different browsers.
|
||||
|
||||
You can see the complete code in the [Minimal JS Web Chat App](/examples/min-js-web-chat).
|
Loading…
Reference in New Issue