Skip to main content

Build React DApps Using @waku/react

caution

Currently, the JavaScript Waku SDK (@waku/sdk) is NOT compatible with React Native. We plan to add support for React Native in the future.

The @waku/react package provides components and UI adapters to integrate @waku/sdk into React applications effortlessly. This guide provides detailed steps for using @waku/react in your project.

Install the dependencies​

First, set up a project using any production-grade React framework or an existing React application. For this guide, we will create a boilerplate using ViteJS:

npm create vite@latest [PROJECT DIRECTORY] -- --template react

Next, install the required packages for integrating @waku/sdk using your preferred package manager:

npm install @waku/react @waku/sdk protobufjs

Initialise the Waku provider​

In the main.jsx file, which serves as the entry point for a React app, we will set up the LightNodeProvider context provider to wrap the entire application within the Waku provider. Import the following on top of your file:

src/main.jsx
import { LightNodeProvider } from "@waku/react";

// Set the Light Node options
const NODE_OPTIONS = { defaultBootstrap: true };

ReactDOM.createRoot(document.getElementById('root')).render(
// Use the Light Node context provider
<React.StrictMode>
<LightNodeProvider options={NODE_OPTIONS}>
<App />
</LightNodeProvider>
</React.StrictMode>,
)

Next, create and start a Light Node using the useWaku() function within the App.jsx file:

src/App.jsx
import { useWaku } from "@waku/react";

function App() {
// Create and start a Light Node
const { node, error, isLoading } = useWaku();

// "node" is the created Light Node
// "error" captures any error that occurs during node creation
// "isLoading" indicates whether the node is still being created
}

Build the application interface​

Let's build a user interface for sending messages and viewing past messages, modify the App.jsx file with the following code block:

src/App.jsx
import { useState, useEffect } from 'react';
import { useWaku } from "@waku/react";
import { createEncoder, createDecoder } from "@waku/sdk";
import protobuf from 'protobufjs';
import './App.css'

function App() {
const [inputMessage, setInputMessage] = useState("");
const [messages, setMessages] = useState([]);

// Update the inputMessage state as the user input changes
const handleInputChange = (e) => {
setInputMessage(e.target.value);
};

// Create and start a Light Node
const { node, error, isLoading } = useWaku();

// Create a message encoder and decoder
const contentTopic = "/waku-react-guide/1/chat/proto";
const encoder = createEncoder({ contentTopic });
const decoder = createDecoder(contentTopic);

// Create a message structure using Protobuf
const DataPacket = new protobuf.Type("DataPacket")
.add(new protobuf.Field("timestamp", 1, "uint64"))
.add(new protobuf.Field("message", 2, "string"));

// Send the message using Light Push
const sendMessage = async () => {}

return (
<>
<div className="chat-interface">
<h1>Waku React Demo</h1>
<div className="chat-body">
{messages.map((message, index) => (
<div key={index} className="chat-message">
<span>{new Date(message.timestamp).toUTCString()}</span>
<div className="message-text">{message.message}</div>
</div>
))}
</div>
<div className="chat-footer">
<input
type="text"
id="message-input"
value={inputMessage}
onChange={handleInputChange}
placeholder="Type your message..."
/>
<button className="send-button" onClick={sendMessage}>Send</button>
</div>
</div>
</>
)
}

export default App
info

In the code above, we also created a message encoder and decoder using the createEncoder() and createDecoder() functions, along with the application message structure with Protobuf.

Next, modify the App.css file with the following code block:

src/App.css
#root {
margin: 0 auto;
}

.chat-interface {
display: flex;
flex-direction: column;
height: 100vh;
border: 1px solid #ccc;
}

.chat-body {
flex-grow: 1;
overflow-y: auto;
padding: 10px;
}

.message-text {
background-color: #f1f1f1;
color: #000;
padding: 10px;
margin-bottom: 10px;
}

.chat-footer {
display: flex;
padding: 10px;
background-color: #f1f1f1;
align-items: center;
}

#message-input {
flex-grow: 1;
border-radius: 4px;
padding: 10px;
margin-right: 10px;
}

.send-button {
background-color: #007bff;
border-radius: 4px;
}

Send messages using light push​

To send messages in our application, we need to modify the sendMessage() function to serialize user input into our Protobuf structure and push it to the network using the useLightPush() function:

src/App.jsx
import { useLightPush } from "@waku/react";

function App() {
// Bind push method to a node and encoder
const { push } = useLightPush({ node, encoder });

// Send the message using Light Push
const sendMessage = async () => {
if (!push || inputMessage.length === 0) return;

// Create a new message object
const timestamp = Date.now();
const protoMessage = DataPacket.create({
timestamp: timestamp,
message: inputMessage
});

// Serialise the message and push to the network
const payload = DataPacket.encode(protoMessage).finish();
const { recipients, errors } = await push({ payload, timestamp });

// Check for errors
if (errors.length === 0) {
setInputMessage("");
console.log("MESSAGE PUSHED");
} else {
console.log(errors);
}
};
}

Receive messages using filter​

To display messages in our application, we need to use the useFilterMessages() function to create a Filter subscription, receive incoming messages, and render them in our interface:

src/App.jsx
import { useFilterMessages } from "@waku/react";

function App() {
// Receive messages from Filter subscription
const { messages: filterMessages } = useFilterMessages({ node, decoder });

// Render the list of messages
useEffect(() => {
setMessages(filterMessages.map((wakuMessage) => {
if (!wakuMessage.payload) return;
return DataPacket.decode(wakuMessage.payload);
}));
}, [filterMessages]);
}

Retrieve messages using store​

To display messages from the past, we need to retrieve them from the Store protocol using the useStoreMessages() function when our application initialises and then render them alongside newly received messages:

src/App.jsx
import { useFilterMessages, useStoreMessages } from "@waku/react";

function App() {
// Query Store peers for past messages
const { messages: storeMessages } = useStoreMessages({ node, decoder });

// Receive messages from Filter subscription
const { messages: filterMessages } = useFilterMessages({ node, decoder });

// Render both past and new messages
useEffect(() => {
const allMessages = storeMessages.concat(filterMessages);
setMessages(allMessages.map((wakuMessage) => {
if (!wakuMessage.payload) return;
return DataPacket.decode(wakuMessage.payload);
}));
}, [filterMessages, storeMessages]);
}

You can also configure a specific Store peer when creating the node, which is useful when running your own Store node or using a specific node in the network:

const node = await createLightNode({ 
defaultBootstrap: true,
store: {
peer: "/ip4/1.2.3.4/tcp/1234/p2p/16Uiu2HAm..." // multiaddr or PeerId of your Store node
}
});

If the specified Store peer is not available, the node will fall back to using random Store peers in the network.

info

To explore the available Store query options, have a look at the Retrieve Messages Using Store Protocol guide.

tip

You have successfully integrated @waku/sdk into a React application using the @waku/react package. Have a look at the web-chat example for a working demo and the Building a Tic-Tac-Toe Game with Waku tutorial to learn more.