mirror of
https://github.com/waku-org/js.waku.guide.git
synced 2025-02-22 11:48:11 +00:00
Upgrade React guides for react-scripts 5.0.0 (#19)
This commit is contained in:
parent
e3b7cf9a3d
commit
10be3e4e7f
@ -17,8 +17,140 @@ For this guide, we are using a single content topic: `/min-react-js-chat/1/chat/
|
||||
Create a new React app:
|
||||
|
||||
```shell
|
||||
npx create-react-app min-react-js-chat
|
||||
cd min-react-js-chat
|
||||
npx create-react-app relay-reactjs-chat
|
||||
cd relay-reactjs-chat
|
||||
```
|
||||
|
||||
## `BigInt`
|
||||
|
||||
Some of js-waku's dependencies use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
|
||||
that is [only supported by modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility).
|
||||
|
||||
To ensure that `react-scripts` properly transpile your webapp code, update the `package.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not ie <= 99",
|
||||
"not android <= 4.4.4",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Setup polyfills
|
||||
|
||||
A number of Web3 dependencies need polyfills.
|
||||
Said polyfills must be explicitly declared when using webpack 5.
|
||||
|
||||
The latest `react-scripts` version uses webpack 5.
|
||||
|
||||
We will describe below a method to configure polyfills when using `create-react-app`/`react-scripts` or webpack 5.
|
||||
This may not be necessary if you do not use `react-scripts` or if you use webpack 4.
|
||||
|
||||
Start by installing the polyfill libraries:
|
||||
|
||||
```shell
|
||||
npm install assert buffer crypto-browserify stream-browserify
|
||||
```
|
||||
|
||||
### Webpack 5
|
||||
|
||||
If you directly use webpack 5,
|
||||
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).
|
||||
|
||||
### cra-webpack-rewired
|
||||
|
||||
An alternative is to let `react-scripts` control the webpack 5 config and only override some elements using `cra-webpack-rewired`.
|
||||
|
||||
Install `cra-webpack-rewired`:
|
||||
|
||||
```shell
|
||||
npm install -D cra-webpack-rewired
|
||||
```
|
||||
|
||||
Create a `config/webpack.extend.js` file at the root of your app:
|
||||
|
||||
```js
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
dev: (config) => {
|
||||
// Override webpack 5 config from react-scripts to load polyfills
|
||||
if (!config.resolve) config.resolve = {};
|
||||
if (!config.resolve.fallback) config.resolve.fallback = {};
|
||||
Object.assign(config.resolve.fallback, {
|
||||
buffer: require.resolve('buffer'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
});
|
||||
|
||||
if (!config.plugins) config.plugins = [];
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.ENV': JSON.stringify('dev'),
|
||||
})
|
||||
);
|
||||
config.plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser.js',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
})
|
||||
);
|
||||
|
||||
if (!config.ignoreWarnings) config.ignoreWarnings = [];
|
||||
config.ignoreWarnings.push(/Failed to parse source map/);
|
||||
|
||||
return config;
|
||||
},
|
||||
prod: (config) => {
|
||||
// Override webpack 5 config from react-scripts to load polyfills
|
||||
if (!config.resolve) config.resolve = {};
|
||||
if (!config.resolve.fallback) config.resolve.fallback = {};
|
||||
Object.assign(config.resolve.fallback, {
|
||||
buffer: require.resolve('buffer'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
});
|
||||
|
||||
if (!config.plugins) config.plugins = [];
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.ENV': JSON.stringify('prod'),
|
||||
})
|
||||
);
|
||||
config.plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser.js',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
})
|
||||
);
|
||||
|
||||
if (!config.ignoreWarnings) config.ignoreWarnings = [];
|
||||
config.ignoreWarnings.push(/Failed to parse source map/);
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Use `cra-webpack-rewired` in the `package.json`, instead of `react-scripts`:
|
||||
|
||||
```
|
||||
"scripts": {
|
||||
- "start": "react-scripts start",
|
||||
- "build": "react-scripts build",
|
||||
- "test": "react-scripts test",
|
||||
- "eject": "react-scripts eject"
|
||||
+ "start": "cra-webpack-rewired start",
|
||||
+ "build": "cra-webpack-rewired build",
|
||||
+ "test": "cra-webpack-rewired test",
|
||||
+ "eject": "cra-webpack-rewired eject"
|
||||
},
|
||||
```
|
||||
|
||||
Then, install [js-waku](https://npmjs.com/package/js-waku):
|
||||
@ -33,15 +165,6 @@ Start the dev server and open the dApp in your browser:
|
||||
npm run start
|
||||
```
|
||||
|
||||
Note: We have noticed some [issues](https://github.com/status-im/js-waku/issues/165) 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.
|
||||
@ -76,12 +199,13 @@ function App() {
|
||||
return (
|
||||
<div className='App'>
|
||||
<header className='App-header'>
|
||||
// Display the status on the web page
|
||||
<p>{wakuStatus}</p>
|
||||
<p>Waku node's status: {wakuStatus}</p>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
# Wait to be connected
|
||||
@ -136,9 +260,9 @@ Create a function that takes the Waku instance and a message to send:
|
||||
```js
|
||||
import { WakuMessage } from 'js-waku';
|
||||
|
||||
const ContentTopic = `/min-react-js-chat/1/chat/proto`;
|
||||
const ContentTopic = `/relay-reactjs-chat/1/chat/proto`;
|
||||
|
||||
function sendMessage(message, timestamp, waku) {
|
||||
function sendMessage(message, waku, timestamp) {
|
||||
const time = timestamp.getTime();
|
||||
|
||||
// Encode to protobuf
|
||||
@ -184,7 +308,7 @@ function App() {
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<p>{wakuStatus}</p>
|
||||
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}> // Grey the button is Waku is not yet ready.
|
||||
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}>
|
||||
Send Message
|
||||
</button>
|
||||
</header>
|
||||
@ -302,4 +426,4 @@ function App() {
|
||||
And Voilà! You should now be able to send and receive messages.
|
||||
Try out by opening the app from different browsers.
|
||||
|
||||
You can see the complete code in the [Minimal ReactJS Chat App](https://github.com/status-im/js-waku/tree/main/examples/min-react-js-chat).
|
||||
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).
|
||||
|
@ -33,8 +33,140 @@ Check out the [how to choose a content topic guide](/docs/guides/01_choose_conte
|
||||
Create a new React app:
|
||||
|
||||
```shell
|
||||
npx create-react-app my-app
|
||||
cd my-app
|
||||
npx create-react-app store-reactjs-chat
|
||||
cd store-reactjs-chat
|
||||
```
|
||||
|
||||
## `BigInt`
|
||||
|
||||
Some of js-waku's dependencies use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
|
||||
that is [only supported by modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility).
|
||||
|
||||
To ensure that `react-scripts` properly transpile your webapp code, update the `package.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not ie <= 99",
|
||||
"not android <= 4.4.4",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Setup polyfills
|
||||
|
||||
A number of Web3 dependencies need polyfills.
|
||||
Said polyfills must be explicitly declared when using webpack 5.
|
||||
|
||||
The latest `react-scripts` version uses webpack 5.
|
||||
|
||||
We will describe below a method to configure polyfills when using `create-react-app`/`react-scripts` or webpack 5.
|
||||
This may not be necessary if you do not use `react-scripts` or if you use webpack 4.
|
||||
|
||||
Start by installing the polyfill libraries:
|
||||
|
||||
```shell
|
||||
npm install assert buffer crypto-browserify stream-browserify
|
||||
```
|
||||
|
||||
### Webpack 5
|
||||
|
||||
If you directly use webpack 5,
|
||||
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).
|
||||
|
||||
### cra-webpack-rewired
|
||||
|
||||
An alternative is to let `react-scripts` control the webpack 5 config and only override some elements using `cra-webpack-rewired`.
|
||||
|
||||
Install `cra-webpack-rewired`:
|
||||
|
||||
```shell
|
||||
npm install -D cra-webpack-rewired
|
||||
```
|
||||
|
||||
Create a `config/webpack.extend.js` file at the root of your app:
|
||||
|
||||
```js
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
dev: (config) => {
|
||||
// Override webpack 5 config from react-scripts to load polyfills
|
||||
if (!config.resolve) config.resolve = {};
|
||||
if (!config.resolve.fallback) config.resolve.fallback = {};
|
||||
Object.assign(config.resolve.fallback, {
|
||||
buffer: require.resolve('buffer'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
});
|
||||
|
||||
if (!config.plugins) config.plugins = [];
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.ENV': JSON.stringify('dev'),
|
||||
})
|
||||
);
|
||||
config.plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser.js',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
})
|
||||
);
|
||||
|
||||
if (!config.ignoreWarnings) config.ignoreWarnings = [];
|
||||
config.ignoreWarnings.push(/Failed to parse source map/);
|
||||
|
||||
return config;
|
||||
},
|
||||
prod: (config) => {
|
||||
// Override webpack 5 config from react-scripts to load polyfills
|
||||
if (!config.resolve) config.resolve = {};
|
||||
if (!config.resolve.fallback) config.resolve.fallback = {};
|
||||
Object.assign(config.resolve.fallback, {
|
||||
buffer: require.resolve('buffer'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
});
|
||||
|
||||
if (!config.plugins) config.plugins = [];
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.ENV': JSON.stringify('prod'),
|
||||
})
|
||||
);
|
||||
config.plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser.js',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
})
|
||||
);
|
||||
|
||||
if (!config.ignoreWarnings) config.ignoreWarnings = [];
|
||||
config.ignoreWarnings.push(/Failed to parse source map/);
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Use `cra-webpack-rewired` in the `package.json`, instead of `react-scripts`:
|
||||
|
||||
```
|
||||
"scripts": {
|
||||
- "start": "react-scripts start",
|
||||
- "build": "react-scripts build",
|
||||
- "test": "react-scripts test",
|
||||
- "eject": "react-scripts eject"
|
||||
+ "start": "cra-webpack-rewired start",
|
||||
+ "build": "cra-webpack-rewired build",
|
||||
+ "test": "cra-webpack-rewired test",
|
||||
+ "eject": "cra-webpack-rewired eject"
|
||||
},
|
||||
```
|
||||
|
||||
Then, install [js-waku](https://npmjs.com/package/js-waku):
|
||||
@ -49,7 +181,8 @@ Start the dev server and open the dApp in your browser:
|
||||
npm run start
|
||||
```
|
||||
|
||||
Note: We have noticed some [issues](https://github.com/status-im/js-waku/issues/165) with React bundling due to `npm` pulling an old version of babel.
|
||||
{{< hint info >}}
|
||||
We have noticed some [issues](https://github.com/status-im/js-waku/issues/165) 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:
|
||||
|
||||
@ -57,6 +190,7 @@ character not being valid, try cleaning up and re-installing your dependencies:
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
{{< /hint >}}
|
||||
|
||||
# Create Waku Instance
|
||||
|
||||
@ -90,12 +224,13 @@ function App() {
|
||||
return (
|
||||
<div className='App'>
|
||||
<header className='App-header'>
|
||||
// Display the status on the web page
|
||||
<p>{wakuStatus}</p>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
# Wait to be connected
|
||||
@ -217,40 +352,43 @@ const processMessages = (retrievedMessages) => {
|
||||
};
|
||||
```
|
||||
|
||||
Finally, pass `processMessage` in `WakuStore.queryHistory` as the `callback` value:
|
||||
Pass `processMessage` in `WakuStore.queryHistory` as the `callback` value:
|
||||
|
||||
```js
|
||||
waku.store
|
||||
.queryHistory([ContentTopic], { callback: processMessages });
|
||||
```
|
||||
|
||||
All together, you should now have:
|
||||
Finally, create a `Messages` component to render the messages:
|
||||
|
||||
```js
|
||||
const ContentTopic = '/toy-chat/2/huilong/proto';
|
||||
```tsx
|
||||
function Messages(props) {
|
||||
return props.messages.map(({ text, timestamp, nick }) => {
|
||||
return (
|
||||
<li>
|
||||
({formatDate(timestamp)}) {nick}: {text}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function formatDate(timestamp) {
|
||||
return timestamp.toLocaleString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Use `Messages` in the `App` function:
|
||||
|
||||
```tsx
|
||||
function App() {
|
||||
// [..]
|
||||
// Store messages in the state
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (wakuStatus !== 'Connected') return;
|
||||
|
||||
const processMessages = (retrievedMessages) => {
|
||||
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
|
||||
|
||||
setMessages((currentMessages) => {
|
||||
return currentMessages.concat(messages.reverse());
|
||||
});
|
||||
};
|
||||
|
||||
waku.store
|
||||
.queryHistory([ContentTopic], { callback: processMessages })
|
||||
.catch((e) => {
|
||||
console.log('Failed to retrieve messages', e);
|
||||
});
|
||||
}, [waku, wakuStatus]);
|
||||
|
||||
return (
|
||||
<div className='App'>
|
||||
@ -264,13 +402,138 @@ function App() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
All together, you should now have:
|
||||
|
||||
```js
|
||||
import { Waku } from 'js-waku';
|
||||
import * as React from 'react';
|
||||
import protons from 'protons';
|
||||
|
||||
const ContentTopic = '/toy-chat/2/huilong/proto';
|
||||
|
||||
const proto = protons(`
|
||||
message ChatMessage {
|
||||
uint64 timestamp = 1;
|
||||
string nick = 2;
|
||||
bytes text = 3;
|
||||
}
|
||||
`);
|
||||
|
||||
function App() {
|
||||
const [waku, setWaku] = React.useState(undefined);
|
||||
const [wakuStatus, setWakuStatus] = React.useState('None');
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
|
||||
// Start Waku
|
||||
React.useEffect(() => {
|
||||
// If Waku status not None, it means we are already starting Waku
|
||||
if (wakuStatus !== 'None') return;
|
||||
|
||||
setWakuStatus('Starting');
|
||||
|
||||
// Create Waku
|
||||
Waku.create({ bootstrap: true }).then((waku) => {
|
||||
// Once done, put it in the state
|
||||
setWaku(waku);
|
||||
// And update the status
|
||||
setWakuStatus('Connecting');
|
||||
});
|
||||
}, [waku, wakuStatus]);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!waku) return;
|
||||
|
||||
if (wakuStatus === 'Connected') return;
|
||||
|
||||
waku.waitForConnectedPeer().then(() => {
|
||||
setWakuStatus('Connected');
|
||||
});
|
||||
}, [waku, wakuStatus]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (wakuStatus !== 'Connected') return;
|
||||
|
||||
const processMessages = (retrievedMessages) => {
|
||||
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
|
||||
|
||||
setMessages((currentMessages) => {
|
||||
return currentMessages.concat(messages.reverse());
|
||||
});
|
||||
};
|
||||
|
||||
waku.store
|
||||
.queryHistory([ContentTopic], { callback: processMessages })
|
||||
.catch((e) => {
|
||||
console.log('Failed to retrieve messages', e);
|
||||
});
|
||||
}, [waku, wakuStatus]);
|
||||
|
||||
return (
|
||||
<div className='App'>
|
||||
<header className='App-header'>
|
||||
<h2>{wakuStatus}</h2>
|
||||
<h3>Messages</h3>
|
||||
<ul>
|
||||
<Messages messages={messages} />
|
||||
</ul>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
function decodeMessage(wakuMessage) {
|
||||
if (!wakuMessage.payload) return;
|
||||
|
||||
const { timestamp, nick, text } = proto.ChatMessage.decode(
|
||||
wakuMessage.payload
|
||||
);
|
||||
|
||||
// All fields in protobuf are optional so be sure to check
|
||||
if (!timestamp || !text || !nick) return;
|
||||
|
||||
const time = new Date();
|
||||
time.setTime(timestamp);
|
||||
|
||||
const utf8Text = Buffer.from(text).toString('utf-8');
|
||||
|
||||
return { text: utf8Text, timestamp: time, nick };
|
||||
}
|
||||
|
||||
function Messages(props) {
|
||||
return props.messages.map(({ text, timestamp, nick }) => {
|
||||
return (
|
||||
<li>
|
||||
({formatDate(timestamp)}) {nick}: {text}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function formatDate(timestamp) {
|
||||
return timestamp.toLocaleString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Note that `WakuStore.queryHistory` select an available store node for you.
|
||||
However, it can only select a connected node, which is why the bootstrapping is necessary.
|
||||
It will throw an error if no store node is available.
|
||||
|
||||
If no message are returned,
|
||||
then you can use https://status-im.github.io/js-waku/ to send some messages on the
|
||||
toy chat topic and refresh your app.
|
||||
|
||||
## Filter messages by send time
|
||||
|
||||
By default, Waku Store nodes store messages for 30 days.
|
||||
|
Loading…
x
Reference in New Issue
Block a user