6 React Mistakes to Avoid
As a manager with a few years of development experience, I often have junior developers or interns come to me to try and understand why their code is not behaving as they expect. When learning to code in React, that often stems from a lack of understanding of how useState behaves. Here are six mistakes that I've seen on the developers' journey to understanding how useState
works.
Not enough state
This was the first mistake made by a (very) junior developer who had not yet understood when a React component function is called. He had changed a variable in a onClick
callback and then wondered why the change was not taken into account on the screen. As a rule, anything external to the application should be stored somewhere, be it the fact that a user has clicked or the return from an API call. "You need to store this in the state", I told him "otherwise it won't be remembered between frames".
Too much state
The next mistake was a step up from the previous one (by the same guy) and a pendulum swing the other way. He was so intent on ensuring that everything was safely stored that he also included calculated values that were simply the result of other states. Of course, if it were simply a question of messy code he might not have been looking for help. But the display was only being updated every other time he clicked. Looking through the code I saw he was doing something like this:
if (condition) setScore(score +1);
if (score > 0) setButtonActive(true);
In the code above, score
has not changed between the two lines (because we are in the same render frame), moreover, the second line was then storing something which could be simply inferred from the state variable, which was why the display was not being updated. "You don't need to store everything", I told him, "logic that you can infer from other state variables doesn't need to be stored".
Updating the same state several times
At another point in his code, my friend had something which looked like this: :
if (condition) setScore(score +1);
// and then a few lines later
if (condition2) setScore(score +1);
Now, the same problem was occurring, where the state was not being updated between the two, which was causing other problems. One way around this might have been to create a proxy variable that we would only update at the end, however, the state update function allows us to pass a function that allows us to manipulate the state with its future value (since the state updates are batched asynchronously).
if (condition) setScore((previous) => previous +1);
// and then a few lines later
if (condition2) setScore((previous) => previous +1);
A point that I didn't raise with him, because he's not quite there yet, is the fact that when the state has interdependent variables which need to be updated in a coordinated or nontrivial way, it might be best to call on useReducer
. But he's not there. Yet.
Redefining too much in the render
A few days later, he was back. He had a form where he was convinced he was doing everything right, and yet the state was being reinitialized every time he entered data into the HTML form. Now at this point, I want to make clear that this is a person who is both very bright and very nice. However, he had just started learning React so he was making pretty much every mistake that was to be made, and I was starting to doubt the wisdom of making him develop using React. But it was an internship, and he had come to learn. And experience is often simply the sum of past mistakes, so by that standard… everything was going very well.
He had taken to heart my advice about recalculating stuff that did not need to be stored. But he had been a bit too enthusiastic about it all. The conversation went something like this:
— So wait, where does this component start?
–– Right at the top of the file, here.
–– And where does it end? I can't find the end.
— It's here at the bottom of the file.
— And what is all this in the middle?
— It's the functions and constants I've defined, and the component for the HTML form. Because I need the state in this component to be shared with the main component.
Hopefully, he did not see the look of despair I'm pretty sure must have been on my face.
"All the constants and all the functions that just provide logic without manipulating the state, can all be moved out of the component, to a separate file. You can just import your entry points and use them." I told him. "And the Form component redefined in the main component… well you're redefining it completely every frame, so you're actually showing a new component at every update."
We ended up doing a lot of refactoring on that bit of code.
Relying solely on initial props to define the state
This one, I must confess, I have been personally guilty of. I had created a component and that basically showed a circular progress bar based on the props I was passing down to it. So it was storing its state like this :
const [progress, setProgress] = useState(props.init);
Of course, the problem with this is that any change in the props won't affect the state, once the first initialization is done.
Here there are two possible solutions, which depend on what exactly is being done in the component. If the component doesn't have any internal logic that updates the state, you don't actually need to store the state. But in my specific case, I did need the state, so I used useEffect
:
const [progress, setProgress] = useState(props.init);
useEffect(()=> {
setProgress(props.init);
}, [props.init]);
Updating the state with a mutated object
This one is a classic mistake that stems from a lack of understanding of how objects work in JavaScript, and more specifically the fact that mutating objects does not trigger React's change detection. This is because object assignment is by reference, i.e. when you assign an object to a variable you're actually just storing the pointer to the object in your variable, so two variables can be pointing to the same object.
let a = {name:"Bob"};
let b = a;
b.name = "Alice";
// here a==b and a.name == "Alice";
In React terms, this means that doing something like this will not update the display, because the value of the state variable does not change, it's always pointing to the same object:
const [state, setState]=useState({score:0});
const onClick =() => {
state.score += 1;
setState(state);
}
The solution, of course (other than using a scalar in the example above, but bear with me) is to create a copy of the state, for example with the spread operator or by declaring a new object:
const [state, setState]=useState({score:0});
const onClick =() => {
setState({..state, score: state.score+1});
}
Key Takeways
Although it was a rocky road, my colleague had fun learning all about React. And I'm pretty sure I made at least as many mistakes as he did, I sometimes cringe when I see old code of mine. And to be perfectly honest, I gained a lot from the experience. Explaining how things work is a great way to keep learning, and a gratifying experience.
We help you better understand software development. Receive the latest blog posts, videos, insights and news from the frontlines of web development