I am trying to get realtime data from bitstamp API and store in my orders state. The page gets stuck in a loop and drains resources and react doesn't re-render the page when the orders state change. I can see the data if I log it to the console
This is what I have implemented so far.
const [loading, setLoading] = useState(true);
const [orders, setOrders] = useState([]);
const [subscription, setSubscription] = useState({
event: 'bts:subscribe',
data: {
channel: 'order_book_btcusd'
}
});
const ws = new WebSocket('wss://ws.bitstamp');
const initWebsocket = () => {
ws.onopen = () => {
ws.send(JSON.stringify(subscription));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
switch (response.event) {
case 'data':
setOrders(response.data);
setLoading(false);
break;
case 'bts:request_reconnect':
initWebsocket();
break;
default:
break;
}
};
ws.onclose = () => {
initWebsocket();
};
};
useEffect(() => {
initWebsocket();
}, [orders, subscription]);
console.log(orders);
const showResult = () => {
orders.bids.map((el, index) => (
<tr key={index}>
<td> {el[0]} </td>
<td> {el[1]} </td>
</tr>
));
};
I am trying to get realtime data from bitstamp API and store in my orders state. The page gets stuck in a loop and drains resources and react doesn't re-render the page when the orders state change. I can see the data if I log it to the console
This is what I have implemented so far.
const [loading, setLoading] = useState(true);
const [orders, setOrders] = useState([]);
const [subscription, setSubscription] = useState({
event: 'bts:subscribe',
data: {
channel: 'order_book_btcusd'
}
});
const ws = new WebSocket('wss://ws.bitstamp');
const initWebsocket = () => {
ws.onopen = () => {
ws.send(JSON.stringify(subscription));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
switch (response.event) {
case 'data':
setOrders(response.data);
setLoading(false);
break;
case 'bts:request_reconnect':
initWebsocket();
break;
default:
break;
}
};
ws.onclose = () => {
initWebsocket();
};
};
useEffect(() => {
initWebsocket();
}, [orders, subscription]);
console.log(orders);
const showResult = () => {
orders.bids.map((el, index) => (
<tr key={index}>
<td> {el[0]} </td>
<td> {el[1]} </td>
</tr>
));
};
Share
edited Sep 29, 2019 at 21:08
SAMA BALA
asked Sep 29, 2019 at 14:06
SAMA BALASAMA BALA
713 silver badges11 bronze badges
2 Answers
Reset to default 3This is happening because useEffect
execute its callback after each render cycle i.e it runs both after the first render and after every update. So for every first message received it is opening a new WebSocket connection and storing the data in the state which is causing a loop.
You can read more about useEffect here
Edited:-
useEffect(() => {
initWebsocket();
}, [orders, subscription]);
The optional second argument to useEffect
is used to detect if anything has changed or not (basically it pares prev state/props and given state/props) and it calls the effect whenever there is a change in value.
So on every orders
state update, this effect will get called and which in turn causes a loop.
Solution:-
But in your case, you want to establish WebSocket connection only once after the ponent has mounted and keep listening to the ining data irrespective of any state or prop change. You can pass an empty [] such that it gets called only once on mount and unmount.
useEffect(() => {
initWebsocket();
// cleanup method which will be called before next execution. in your case unmount.
return () => {
ws.close
}
}, []);
From doc:-
This requirement is mon enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect.
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar ponentDidMount and ponentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often.
In useEffect
, check if the WebSocket connection is closed before initializing it.
If you are confused with the working of react hooks, you can use class ponents and initialize your WebSocket connection in ponentDidMount
and ponentDidUpdate
(Check if the connection is closed and initialize it).
PS: I have implemented a simple Chat Application using React and WebSockets.
https://github./Nikhil-Kumaran/ChatApp
Go through the repo to have a better idea.
Related ponent: https://github./Nikhil-Kumaran/ChatApp/blob/master/src/WebSockets.js