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.net');
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>
));
};
This 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 compares 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 component has mounted and keep listening to the incoming 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 common 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 componentDidMount and componentWillUnmount 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 components and initialize your WebSocket connection in componentDidMount and componentDidUpdate(Check if the connection is closed and initialize it).
PS:
I have implemented a simple Chat Application using React and WebSockets.
https://github.com/Nikhil-Kumaran/ChatApp
Go through the repo to have a better idea.
Related component: https://github.com/Nikhil-Kumaran/ChatApp/blob/master/src/WebSockets.js
Related
I have a React application. I am using Jest and React Testing library for unit testing.
I have to test a component. In the useEffect of the component, there is an API call made and once the response is received, we update the component's local state.
const [data, setData] = useState({})
useEffect(()=>{
// Make API call to a custom fetch hook
},[])
useEffect(()=>{
setData(response.data) //response data is a JSON object
},[response])
The test files code snippet is as below -
const {getByTestId} = render(<MyComponent></MyComponent>)
I have not put any assertions yet because of the inifinite running test cases
What have I done? I have been able to mock the fetch call and execute setData.
The problem - The tests keep running forever. But if I change the response.data to some boolean or string or number, the tests do not run infinitly.
Also, if I put a dummy object in the initialization of the state, the tests run fine.
const [data, setData] = useState({
name: 'Test',
Age: '99'
})
Providing an object as dependency in useEffect is not a good idea, since even if the data in object remains same, on every render -- object reference changes - the effect will run again (even if the data within stays same).
A workaround for this would be stringifying the dependency with JSON.stringify. (although doing on data containing some objects like dates, symbols, null or undefined etc. isn't recommended)
useEffect(() => {
setData(response.data)
}, [JSON.stringify(response)]);
Doing above shouldn't affect your UI.
Other solution would be to store the previous value of response and compare before you do setData. You can use usePrevious hook:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.
There is the case , i have a modal which shows some data from its state ( an array ) , and it's state is getting set in componentDidMount() function like docs suggests. I need to show updated data every time when modal opened up.So i was able to do that with componentWillReceiveProps(nextProps) function , like i showed below .
But what if i want to migrate to getDerivedStateFromProps function ? How am i going to achieve same behaviour with it ?
Here is the component code simplified for sake :
export class PastOrdersModal extends Component {
constructor(props) {
super(props);
this.state = {
past: {},
isLoading: false,
modalVisible: false
};
}
async componentWillReceiveProps(nextProps) {
const response = await this.fetchPast();
this.setState({ past: response });
}
async componentDidMount() {
const response = await this.fetchPast();
this.setState({ past: response });
}
...
... some callback functions for handling modalvisible value ....
...
render(){
// here rendering state array with this.state.past.map(......) etc.
}
The fetchPast function makes a GET request to server of mine , all you need to know that it returns an array to me. This is working perfectly. ComponentWillReceiveProps gets called every time because parent component sends modalVisible props everytime.
But componentWillRecieveProps is deprecating and i could not make the same behavior with getDerivedStateFromProps.How should i implement same thing with it.
Note: ı am not going to use redux ,i am not going to use mobx , i know how to use them and it's not what i want. The thing is the behavior i want is soo simple i don't want to pass values another component , i don't want to pass values another screen , i just want to update a simple component that is all but either the framework is pushing it's limits to make simplest tasks hardests thing ever or i am missing really huge point.(probably latter one :) )
Note: I know i am not doing anything with nextProps , that was the only solution i found.
You can write your Component as a function so you can use React Hooks
const PastOrdersModal = (props) => {
const [past, setPast] = useState({});
const [isLoading, setLoading] = useState(false);
const [modalVisible, setModalVisibility] = useState(false);
useEffect(() => {
const fetchPast = async () => {
const response = await fetchPast();
setPast(response);
};
if(modalVisible) fetchPast();
}, [modalVisible])
useEffect(() => {
const fetchPast = async () => {
const response = await fetchPast();
setPast(response);
};
fetchPast();
}, [])
...
... some callback functions for handling modalvisible value ....
...
return <YourComponent />
The variables we created inside [] at the top are the ones we are using for the state. The first one will be the state as itself and the second one is a function responsible for updating that state. The useEffect hook will simulate the life cycle methods for Class Components, it receives a callback that will be executed and a second argument. The second argument is the one that will indicate when it is going to be triggered. For instance, you can see 2 useEffects, the one with the empty array will tell the hook to execute just once, similar as componentDidMount. The other one will be triggered when modalVisible changes, so everytime you change its value it will be executed, that's why we only validate if the modal is visible (is true) we do the fetch, otherwise the fetch won't be executed
react functional component is taking snapshot of state at the time of subscription.
For ex. PFB code.
If i click setSocketHandler button and then press setWelcomeString button. Now if i receive message over socket when i log welcomestring it is empty.
But if i click setWelcomeString button and then click setSocketHandler button. Now if i receive message on socket Welcome is getting logged on console.
I have seen same behaviour in project so just created this simple app to prove.
If i use class component which is commented below.. everything works fine.
So my question is why react functional component is working on a state at the time of reg and not on actual state at the time message is received.
This is very weird. How to make it work in functional component correctly.
import React, {useEffect, useState} from 'react';
import logo from './logo.svg';
import './App.css';
const io = require('socket.io-client');
const socket = io.connect('http://localhost:3000/');
const App : React.FunctionComponent = () => {
const [welcomeString, setWelcomeString] = useState("");
const buttonCliecked = () => {
console.log("clocked button");
setWelcomeString("Welcome")
}
const onsockethandlerclicked = () => {
console.log("socket handler clicked");
socket.on('out', () => {
console.log("Recived message")
console.log(welcomeString);
});
}
return (
<div>
<header className="component-header">User Registration</header>
<label>{welcomeString}</label>
<button onClick={buttonCliecked}>setWelcomeString</button>
<button onClick={onsockethandlerclicked}>setSocketHandler</button>
</div>
);
}
/*class App extends React.Component {
constructor(props) {
super(props);
this.state = {
welcomeString:""
}
}
buttonCliecked = () => {
console.log("clocked button");
this.setState({ welcomeString:"Welcome"})
}
onsockethandlerclicked = () => {
console.log("socket handler clicked");
socket.on('out', () => {
console.log("Recived message")
console.log(this.state.welcomeString);
});
}
render() {
return (
<div>
<header className="component-header">User Registration</header>
<label>{this.state.welcomeString}</label>
<button onClick={this.buttonCliecked}>setwelcomestring</button>
<button onClick={this.onsockethandlerclicked}>setSocketHandler</button>
</div>
);
}
}*/
export default App;
For those of us coming from a Redux background, useReducer can seem deceptively complex and unnecessary. Between useState and context, it’s easy to fall into the trap of thinking that a reducer adds unnecessary complexity for the majority of simpler use cases; however, it turns out useReducer can greatly simplify state management. Let’s look at an example.
As with my other posts, this code is from my booklist project. The use case is that a screen allows users to scan in books. The ISBNs are recorded, and then sent to a rate-limited service that looks up the book info. Since the lookup service is rate limited, there’s no way to guarantee your books will get looked up anytime soon, so a web socket is set up; as updates come in, messages are sent down the ws, and handled in the ui. The ws’s api is dirt simple: the data packet has a _messageType property on it, with the rest of the object serving as the payload. Obviously a more serious project would design something sturdier.
With component classes, the code to set up the ws was straightforward: in componentDidMount the ws subscription was created, and in componentWillUnmount it was torn down. With this in mind, it’s easy to fall into the trap of attempting the following with hooks
const BookEntryList = props => {
const [pending, setPending] = useState(0);
const [booksJustSaved, setBooksJustSaved] = useState([]);
useEffect(() => {
const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
ws.onmessage = ({ data }) => {
let packet = JSON.parse(data);
if (packet._messageType == "initial") {
setPending(packet.pending);
} else if (packet._messageType == "bookAdded") {
setPending(pending - 1 || 0);
setBooksJustSaved([packet, ...booksJustSaved]);
} else if (packet._messageType == "pendingBookAdded") {
setPending(+pending + 1 || 0);
} else if (packet._messageType == "bookLookupFailed") {
setPending(pending - 1 || 0);
setBooksJustSaved([
{
_id: "" + new Date(),
title: `Failed lookup for ${packet.isbn}`,
success: false
},
...booksJustSaved
]);
}
};
return () => {
try {
ws.close();
} catch (e) {}
};
}, []);
//...
};
We put the ws creation in a useEffect call with an empty dependency list, which means it’ll never re-fire, and we return a function to do the teardown. When the component first mounts, our ws is set up, and when the component unmounts, it’s torn down, just like we would with a class component.
The problem
This code fails horribly. We’re accessing state inside the useEffect closure, but not including that state in the dependency list. For example, inside of useEffect the value of pending will absolutely always be zero. Sure, we might call setPending inside the ws.onmessage handler, which will cause that state to update, and the component to re-render, but when it re-renders our useEffect will not re-fire (again, because of the empty dependency list)—as a result that closure will go on closing over the now-stale value for pending.
To be clear, using the Hooks linting rule, discussed below, would have caught this easily. More fundamentally, it’s essential to break with old habits from the class component days. Do not approach these dependency lists from a componentDidMount / componentDidUpdate / componentWillUnmount frame of mind. Just because the class component version of this would have set up the web socket once, in componentDidMount, does not mean you can do a direct translation into a useEffect call with an empty dependency list.
Don’t overthink, and don’t be clever: any value from your render function’s scope that’s used in the effect callback needs to be added to your dependency list: this includes props, state, etc. That said—
The solution
While we could add every piece of needed state to our useEffect dependency list, this would cause the web socket to be torn down, and re-created on every update. This would hardly be efficient, and might actually cause problems if the ws sends down a packet of initial state on creation, that might already have been accounted for, and updated in our ui.
If we look closer, however, we might notice something interesting. Every operation we’re performing is always in terms of prior state. We’re always saying something like “increment the number of pending books,” “add this book to the list of completed,” etc. This is precisely where a reducer shines; in fact, sending commands that project prior state to a new state is the whole purpose of a reducer.
Moving this entire state management to a reducer would eliminate any references to local state within the useEffect callback; let’s see how.
function scanReducer(state, [type, payload]) {
switch (type) {
case "initial":
return { ...state, pending: payload.pending };
case "pendingBookAdded":
return { ...state, pending: state.pending + 1 };
case "bookAdded":
return {
...state,
pending: state.pending - 1,
booksSaved: [payload, ...state.booksSaved]
};
case "bookLookupFailed":
return {
...state,
pending: state.pending - 1,
booksSaved: [
{
_id: "" + new Date(),
title: `Failed lookup for ${payload.isbn}`,
success: false
},
...state.booksSaved
]
};
}
return state;
}
const initialState = { pending: 0, booksSaved: [] };
const BookEntryList = props => {
const [state, dispatch] = useReducer(scanReducer, initialState);
useEffect(() => {
const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
ws.onmessage = ({ data }) => {
let packet = JSON.parse(data);
dispatch([packet._messageType, packet]);
};
return () => {
try {
ws.close();
} catch (e) {}
};
}, []);
//...
};
While slightly more lines, we no longer have multiple update functions, our useEffect body is much more simple and readable, and we no longer have to worry about stale state being trapped in a closure: all of our updates happen via dispatches against our single reducer. This also aids in testability, since our reducer is incredibly easy to test; it’s just a vanilla JavaScript function. As Sunil Pai from the React team puts it, using a reducer helps separate reads, from writes. Our useEffect body now only worries about dispatching actions, which produce new state; before it was concerned with both reading existing state, and also writing new state.
You may have noticed actions being sent to the reducer as an array, with the type in the zero slot, rather than as an object with a type key. Either are allowed with useReducer; this is just a trick Dan Abramov showed me to reduce the boilerplate a bit :)
What about functional setState()
Lastly, some of you may be wondering why, in the original code, I didn’t just do this
setPending(pending => pending - 1 || 0);
rather than
setPending(pending - 1 || 0);
This would have removed the closure problem, and worked fine for this particular use case; however, the minute updates to booksJustSaved needed access to the value of pending, or vice versa, this solution would have broken down, leaving us right where we started. Moreover, I find the reducer version to be a bit cleaner, with the state management nicely separated in its own reducer function.
All in all, I think useReducer() is incredibly under-utilized at present. It’s nowhere near as scary as you might think. Give it a try!
Happy coding!
I have a simple react component that uses react hooks. I'm using useEffect and useState.
The problem is that i realized my API is getting huge amount of hits, after debugging i see that useEffect is running none stop!
This is my very simple code:
function DisplayUser({userId}) {
const [loggedUser, setLoggedUser] = React.useState(null);
React.useEffect(() => {
fetchData(userId).then(user => {
setLoggedUser(user);
})
});
return (
<div>
<div>{loggedUser}</div>
</div>
);
}
Whats happening here is that useEffect is running after each render
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update...
There is a way to do Optimizing Performance by Skipping Effects, you can pass an array as a second argument.
If the array is empty, that means there are no "dependencies" for this effect and it will run only once (quite simillar to componentDidMount).
If the array has values, then the effect will re-run only when those values have changeded (simillar to what we do with componentDidUpdate).
So in your case it is wise to pass an array with the userId value because you'll want to re-run the effect and fetch the user data only when the userId has changed.
function DisplayUser({userId}) {
const [loggedUser, setLoggedUser] = React.useState(null);
React.useEffect(() => {
fetchData(userId).then(user => {
setLoggedUser(user);
})
}, [userId]); // only run when userId changed
return (
<div>
<div>{loggedUser}</div>
</div>
);
}