Build React DApps Using @waku/react
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
- Yarn
npm create vite@latest [PROJECT DIRECTORY] -- --template react
yarn create vite [PROJECT DIRECTORY] --template react
Next, install the required libraries for integrating @waku/sdk
using your preferred package manager:
- NPM
- Yarn
npm install @waku/react @waku/sdk protobufjs
yarn add @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:
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:
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:
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 ChatMessage = new protobuf.Type("ChatMessage")
.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
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:
#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:
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 = ChatMessage.create({
timestamp: timestamp,
message: inputMessage
});
// Serialise the message and push to the network
const payload = ChatMessage.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:
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 ChatMessage.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:
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 ChatMessage.decode(wakuMessage.payload);
}));
}, [filterMessages, storeMessages]);
}
To explore the available Store query options, have a look at the Retrieve Messages Using Store Protocol guide.
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.