docs.waku.org/docs/guides/js-waku/use-waku-react.md

8.6 KiB

title hide_table_of_contents
Build React DApps Using @waku/react true

:::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:

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
npm create vite@latest [PROJECT DIRECTORY] -- --template react
yarn create vite [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
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

:::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:

#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]);
}

:::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. :::