DEV Community

Cover image for The most common mistakes when using React
Alex K.
Alex K.

Posted on • Updated on • Originally published at claritydev.net

The most common mistakes when using React

The article was originally posted on my personal blog.

Answering React-related questions on Stack Overflow, I've noticed that there are a few main categories of issues people have with the library. I've decided to write about the most common ones and show how to handle them in hopes that it'll be helpful to those new to React or anyone in general, who's struggling with its basic concepts. Both pitfalls of using Class based components and Functional components that use hooks are covered interchangeably. 

1. Directly modifying the state

The state in React is considered immutable and therefore should not be directly changed. A special setState method and the setter function from useState hook should be used instead. Consider the following example, where you'd want to update checked field of a particular object in array, based on the state of a checkbox.

    const updateFeaturesList = (e, idx) => {
      listFeatures[idx].checked = e.target.checked;
      setListFeatures(listFeatures);
    };
Enter fullscreen mode Exit fullscreen mode

The issue with this code is that the changes to the state won't be reflected in the UI since the state is updated with the same object reference and therefore it doesn't trigger a re-render. Another important reason for not mutating the state directly is that due to it's asynchronous nature later state updates might override the ones made directly to the state, resulting in some evasive bugs. The correct way in this case would be to use the setter method of useState.

    const updateFeaturesList = (e, idx) => {
      const { checked } = e.target;
      setListFeatures(features => {
        return features.map((feature, index) => {
          if (idx === index) {
            feature = { ...feature, checked };
          }
          return feature;
        });
      });
    };
Enter fullscreen mode Exit fullscreen mode

By using map and object spread we're also making sure that we're not changing the original state items.

2. Setting wrong value types on the initial state

Setting the initial state values to null or an empty string and then accessing properties of that value in render as if it's an object is quite a common mistake. The same goes for not providing default values for nested objects and then trying to access them in render or other component methods.

 

    class UserProfile extends Component {
      constructor(props) {
        super(props);

        this.state = {
          user: null
        };
      }

      componentDidMount() {
        fetch("/api/profile").then(data => {
          this.setState({ user: data });
        });
      }

      render() {
        return (
          <div>
            <p>User name:</p>
            <p>{this.state.user.name}</p> // Cannnot read property 'name' of null
          </div>
        );
      }
    }
Enter fullscreen mode Exit fullscreen mode

A similar error happens with setting value on initial state to an empty array and then trying to access n-th item from it. While the data is being fetched by an API call, the component will be rendered with provided initial state, and trying to access a property on null or undefined element will cause an error. Therefore it is important to have the initial state closely represent the updated state. In our case a correct state initialisation is as follows:

    class UserProfile extends Component {
      constructor(props) {
        super(props);

        this.state = {
          user: {
            name: ""
            // Define other fields as well
          }
        };
      }

      componentDidMount() {
        fetch("/api/profile").then(data => {
          this.setState({ user: data });
        });
      }

      render() {
        return (
          <div>
            <p>User name:</p>
            <p>{this.state.user.name}</p> // Renders without errors
          </div>
        );
      }
    }
Enter fullscreen mode Exit fullscreen mode

From the UX point of view, it's probably best to display some sort of loader until the data is fetched. 

3. Forgetting that setState is asynchronous

Another common mistake is trying to access state value right after setting it.

    handleChange = count => {
      this.setState({ count });
      this.props.callback(this.state.count); // Old state value
    };
Enter fullscreen mode Exit fullscreen mode

Setting new value doesn't happen immediately, normally it's done on the next available render, or can be batched to optimise performance. So accessing a state value after setting it might not reflect the latest updates. This issue can be fixed by using an optional second argument to setState, which is a callback function, called after the state has been updated with its latest values.

 

    handleChange = count => {
      this.setState({ count }, () => {
        this.props.callback(this.state.count); // Updated state value
      });
    };
Enter fullscreen mode Exit fullscreen mode

It's quite different with the hooks though, since the setter function from useState doesn't have a second callback argument akin to that of setState. In this case the official recommended way is to use useEffect hook.

    const [count, setCount] = useState(0)

    useEffect(() => {
      callback(count); // Will be called when the value of count changes
    }, [count, callback]);

    const handleChange = value => {
      setCount(value)
    };
Enter fullscreen mode Exit fullscreen mode

It should be noted that setState is not asynchronous in a way that it returns a promise. So slapping async/await on it or using then won't work (another one of common mistakes). 

4. Incorrectly relying on the current state value for calculating the next state

This issue is related to the one discussed above as it also has to do with the state update being asynchronous.

    handleChange = count => {
      this.setState({ count: this.state.count + 1 }); // Relying on current value of the state to update it
    };
Enter fullscreen mode Exit fullscreen mode

The issue with this approach is that the value of count may not be properly updated at the moment when the new state is being set, which will result in the new state value to be set incorrectly. A correct way here is to use the functional form of setState.

    increment = () => {
      this.setState(state => ({ count: state.count + 1 })); // The latest state value is used
    };
Enter fullscreen mode Exit fullscreen mode

The functional form of setState has a second argument - props at the time the update is applied, which can be used in a similar way as state.  

The same logic applies to the useState hook, where the setter accepts a function as an argument.

    const increment = () => {
     setCount(currentCount => currentCount + 1)
    };
Enter fullscreen mode Exit fullscreen mode

5. Omitting dependency array for useEffect

This is one of the less popular mistakes, but happens nevertheless. Even though there are completely valid cases for omitting dependency array for useEffect, doing so when its callback modifies the state might cause an infinite loop.

6. Passing objects or other values of non-primitive type to the useEffect's dependency array

Similar to the case above, but more subtle mistake, is tracking objects, arrays or other non-primitive values in the effect hook's dependency array. Consider the following code.

 

    const features = ["feature1", "feature2"];

    useEffect(() => {
      // Callback 
    }, [features]);
Enter fullscreen mode Exit fullscreen mode

Here when we pass an array as a dependency, React will store only the reference to it and compare it to the previous reference of the array. However, since it is declared inside the component, features array is recreated on every render, meaning that it's reference will be a new one every time, thus not equal to the one tracked by useEffect. Ultimately, the callback function will be run on each render, even if the array hasn't been changed. This is not an issue with primitive values, like strings and numbers, since they are compared by value and not by reference in JavaScript.

There are a few ways to fix this. First option is to move variable declaration outside of the component, so it won't be recreated every render. However, in some cases this is not possible, for example if we're tracking props or tracked dependency is a part of the component's state. Another option is to use a custom deep compare hook to properly track the dependency references. An easier solution would be to wrap the value into useMemo hook, which would keep the reference during re-renders.

    const features = useMemo(() => ["feature1", "feature2"], []);

    useEffect(() => {
      // Callback 
    }, [features]);
Enter fullscreen mode Exit fullscreen mode

Hopefully this list will help you to avoid the most common React issues and improve understanding of the main pitfalls. 

Got any questions/comments or other kinds of feedback about this post? Let me know in the comments or on Twitter.

Discussion (32)

Collapse
thebouv profile image
Anthony Bouvier

I'll add one more mistake:

Using React when you don't need it.

Collapse
niweera profile image
Nipuna Weerasekara

You hurt my feelings 😆

Collapse
thebouv profile image
Anthony Bouvier

HUG!!!!

Collapse
techbos profile image
TechBos😎

Very true.

Collapse
lexiebkm profile image
Alexander B.K.

I am using it, and I am quite satisfied with the result. My app consists of a lot of modules. I handle myself both of the front end and back end.

Collapse
thebouv profile image
Anthony Bouvier

That's great. I'm not saying React isn't useful. I'm saying it is a tool and some people try to use it for every problem.

There are plenty of projects where React as a choice is simply over-engineering.

Thread Thread
dillionmegida profile image
Dillion Megida

Could you list examples?

I'm getting to think I'm involved here 😕

Thread Thread
lexiebkm profile image
Alexander B.K.

Because my app is a specific SPA, I choose React. As a learning developer, I don't want to write in vanilla JS for some reasons. Learning Angular takes longer time.
I am developing a KPI management system using Balanced Score Card methodology; it deals with massive usage of database (I use mySQL) from which data need to be displayed in a datagrid that must have features such as inline editing and row grouping. For that purpose I use ag-Grid and Tabulator. The app is supposed to be able to display dashboard that contains data visualizations as well as standard reporting (pdf and possibly excel format as well).
So, scalability is my concern since I began to think about using JS framework.

Collapse
c24w profile image
Chris Watson

zing

Collapse
franzisk profile image
Francisco Vieira Souza

And when would that be?

Collapse
victorcorcos profile image
Victor Cordeiro Costa • Edited on

I found an error on the second code snipped. You passed the idx as a parameter, but you used the id instead.

The corrected code...

    const updateFeaturesList = (e, id) => {
      const { checked } = e.target;
      setListFeatures(features => {
        return features.map((feature, index) => {
          if (id === index) {
            feature = { ...feature, checked };
          }
          return feature;
        });
      });
    };

It is just a minor thing, though

Collapse
clarity89 profile image
Alex K. Author

A sharp eyed reader! :) Thank you, I'll fix it :)

Collapse
victorcorcos profile image
Victor Cordeiro Costa

Hehehe, thank you!
By the way, Great post!!
I am learning React from scratch right now and this was in particular useful! <3

Thread Thread
clarity89 profile image
Alex K. Author

Thank you and good luck! :) Be sure to also check the official documentation, it has more details about the points I mentioned.

Collapse
torrao profile image
Ricardo Torrão

Good article!

Just a reminder...React is a library and not a framework 😊

Collapse
vonheikemen profile image
Heiker

I think React is no longer just a library for the view layer. Their scope has a stronger focus on state and side effects, and now with hooks in the picture there is now a "React way" of doing things.

Collapse
clarity89 profile image
Alex K. Author

Ah, semantics... I use those terms interchangeably, but I guess in this case it's good to stick to the official description. I'll update the post :)

Collapse
clarity89 profile image
Alex K. Author

Thanks for the feedback :) My point was more about keeping state and it's usage consistent, e.g. do not declare something as an array and access it as an object. But for small cases like having a name defined on a user object, it sometimes does make sense imo. What I did not mean is to have the whole complex state structure set out in the initial state. In that case it's better to use thorough validations when accessing it or delaying accessing the state until all the data is loaded. Should have probably made it more clear in the article :)

Collapse
chrisachard profile image
Chris Achard

Nice list! Using the functional form of setState is the one that I always forget 🤪

I also like the last example with useMemo - thanks!

Collapse
lexiebkm profile image
Alexander B.K.

Point 1, 3 and 4 are already covered in the Documentation. I only know a little about Hooks, so I skip point 5 and 6 until I finish learning them through the doc before using them in a real project. So far, most of components I create in my current project are class based. Maybe in next projects I will consider using Hooks when appropriate.
There are actually a lot of things I need to master regarding React : Hooks, Redux (especially for global state management) and Webpack; not to mention other technologies for front end and back end. Unfortunately for some reasons I couldn't learn them regularly.

vonheikemen profile image
Heiker • Edited on

You can't really do shit with React without a page to mount it.

Do you mean it's not a framework because you need ReactDOM to mount it in the DOM?

I could think of more reasons why React is not a framework but that is not one.

Collapse
lwatson2 profile image
Logan Watson

Nice article! Love learning stuff like this!

Collapse
hemalr profile image
Hemal • Edited on

Awesome article, a few gotchas I didn't know of. The useMemo one especially 👍

Collapse
teekatwo profile image
Tori Pugh

Great article! I think you've answered one of my questions with useEffect not working. I need to do more research on useMemo now.

Collapse
changoman profile image
Hunter Chang

Nice! I like the useEffect callback example with useState.

Collapse
mudras profile image
mudra

Very nice article

Collapse
lexiebkm profile image
Alexander B.K.

I think there is also one important topic you can discuss :
Controlled vs Uncontrolled component.
In real practices, we have to decide which one we want to use (consistently).

Collapse
kylefilegriffin profile image
Kyle Griffin

Some very helpful tips. Some are pretty obvious but as a learning developer, I'm seeing best practice for general javascript here too, which is good.

Collapse
hyggedev profile image
Chris Hansen

Thankful for this. You basically cleared up how useMemo() works for me. 💯