Sonny Recio 10% Entrepreneur | Fitness Junkie | Software Engineer | Fullstack web developer | Game Developer | Article writer and musician | Lifelong learner

The noob’s guide to useState

8 min read 2274

React Logo

One of the reasons we can use functional components as our main component is because it can now contain its own “state” using Hooks such as useState. Because of this, it is possible to ditch class-based components altogether.

Despite this advantage given by Hooks, it’s still possible to cultivate bad practice while using useState in our functional components. We are still not safe in potential pitfalls we may introduce while constructing our components in function form.

How do you know if you’re using useState wrong? Read on.

Mutating state instead of using setState provided by useState

First of all, mutating state is a big no-no in the React ecosystem because of the fact that it heavily practices the concept of immutability. To demonstrate how you could carelessly mutate the state without knowing, consider the following code snippet:

const [MyValue, setMyValue] = useState(0);

MyValue = 55;

This is considered mutating a state directly. We are heavily violating the rule of thumb in manipulating our state properly because it was supposed to be treated as immutable unless we call the array’s second element, setMyValue.

Because the state value is “read-only,” you cannot modify it this way. It will throw an error:

Other ways that you can mistakenly mutate a state include the following example:

const [myValues, setMyValues] = useState([1,2,3,4,5]);

myValues[2] = 55;
const [myValues, setMyValues] = useState([1,2,3,4,5]);

//map creates new array. But it is still referencing the old array, so therefore in this case we are still mutating myValues array.
const newValues = myValues.map((item, idx) => {
        if(idx === 2) item = 55;

        return item;
});

In this example, you are trying to mutate a state value, which is an array. You may be able to mutate it, but this will not issue a “re-render” in your component, which means the new value will not be shown in your UI.

To show it in real-time, let us give you a working example of mutating an array:

let count = 0;
const App = () => {

  const [stateVal, setStateVal] = React.useState([1,2,3,4,5]);

  const onChangeArrayValues = () => {
    stateVal[count] = "Changed";

    count += 1;

    alert("updated array: " + stateVal);
  }

  return (
    <div>
      <h1>Changing array state values</h1>
      <h2>Array values: {stateVal}</h2>
      {/* <h2>Sum result: {multiplyByThree(5, 5)}</h2> */}


      <button onClick={() => onChangeArrayValues()}>Click to change</button>
    </div>
  );
}

So, as we can see in this example, even though we have mutated the state array, it does not reflect our UI. React is smart enough to know whether the state is set or just simply “mutated.” If it’s mutated, it won’t issue “re-render” in its components to reflect the new state value in our UI.

We made a custom demo for .
No really. Click here to check it out.

The same can be said with object-based state:

const App = () => {

  const [stateVal, setStateVal] = useState({ val1: "Hello world!" });

  return (
    <div>
      <h1 onClick={() => stateVal.val1 = "Mutated value..."}>
                                Test state: {stateVal.val1}
                        </h1>
    </div>
  )
}

https://codepen.io/reciosonny/pen/ExNaagg

We may be able to mutate it without React noticing you’ve mutated it. The same issue will happen as the last example with mutating an array: the new value will not reflect in our UI.

In this example, the state still needs to be set properly using the setState function provided by useState.

This is not unique to state hooks alone. In fact, you can make the same mistake of managing states in a class-based component.

How should you set state?

One way to solve this is to ensure we use an immutable approach like setting state values using a second element from useState, like so:

const [myValues, setMyValues] = useState(0);

setMyValues(55);

This is the official method of setting a state value in an immutable manner. We use the second element, which is a function to set the state.

We can still use this approach to object-based states. However, we must still observe the concept of immutability when modifying such state. This sample code snippet will help you do the trick:

// Using Object.assign method:
const newState = Object.assign({}, state, {[item.id]: item});

// Or using ES6 spread syntax:
const newState = { ...oldState, prop1: "modified value" };

In setting a state for arrays, the best way is to re-create the array you wanted to modify with its changes. This is one of the best ways that I know of for modifying the array:

const [myValues, setMyValues] = useState([1,2,3,4,5]);

// Copying new set of arrays using ES6 spread syntax
const newItems = [...myValues];
newItems[2] = 55; //modifying specific array element 

setMyValues(newItems); //set the new array with modified values

This is how it would look like in real-time.

In this sample code snippet, we’re effectively making sure that we recreate that array, then apply the changes in the specific element we wanted to change. With this method, we are letting React know that a state is being changed in an immutable manner. This will trigger the component “re-render” under the hood.

Passing useState in children component props to use it

Passing useState as props in another component is totally possible. But there’s no benefit in doing so because you can always call useState by importing React at the top of your JavaScript code and call it in all of your components.

Here’s the sample code snippet to demonstrate this:

import React, { Component, useState } from 'react';
import { hot } from "react-hot-loader";

const NewComponent = ({ useStateFn }) => {

  const [val, setVal] = useStateFn(0); //we used useState from the props passed onto this component

  return (
                <div>

            <h2>Value: {val}</h2>
            <br/><br/>
            <button onClick={() => setVal(25)}>Change value</button>
          </div>
        );
}

const App = () => {

  return (
    <div>
      <h1>Hello world!</h1>

                        {/* We passed useState in child component for them to be consumed */}
      <NewComponent useStateFn={useState} />      
    </div>
  )
} 

This is a bad practice, and you should never use useState like this. Also, this could potentially introduce spaghetti code that may make the app much difficult to fix. Avoid this like the plague.

Not putting useState at the top of component body or functions

According to official React docs:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function

Because useState is a hook, we need to put this at the top level of our component, so putting it on areas other than the top level may potentially introduce confusion within our component structure.

Instead of doing this:

const App = () => {

  const onValueChanged = (input) => {
    setVal(input);
  }

  const [val, setVal] = useState(0);

  return (
    <div>
      <h1>Hello world!</h1>
    </div>
  )
}

Do this:

const App = () => {

  const [val, setVal] = useState(0);


  const onValueChanged = (input) => {
    setVal(input);
  }

  return (
    <div>
      <h1>Hello world!</h1>
    </div>
  )
}

Using this best practice will allow us to avoid potential bugs related to calling a state when our app grows larger.

Using useState in class components or regular JavaScript functions

If you have read the rules of hooks in official React docs, they encourage you not to put any hooks such as useState in class or regular JavaScript functions. This is because hooks do not play very well with those, especially in class-based component structures.

Suppose that you still insist on using useState on class-based components, like in this example:

class App extends Component {

  render() {

    const [inputVal, setInputVal] = useState("");

    return (
      <div>
        <input type="text" onChange={(e) => setInputVal(e.target.value)} />

        <h1>Input value: {inputVal}</h1>
      </div>
    );
  }
}

This is what you’ll see:

For this matter, React will immediately notify you that it’s an invalid use-case of using hooks on a class-based component. This means there’s no way you can use hooks such as useState on it.

There are other use-cases that are subtle but are using the wrong implementation of useState, such as using it in plain function expressions. Here’s an example.

const myFunction = (arg1, arg2, arg3) => {
  const [myStateValue, setMyStateValue] = useState("");

  //do logic here...
}

If you recall, the rules of hooks say along those lines:

Don’t call Hooks from regular JavaScript functions

Then this is an invalid use of useState, unless we use that function as a custom hook. A custom hook is also just a JavaScript function, only that this time it has a lifecycle of its own, such as adding useEffect to keep track of changes of its state.

So, instead of a regular function, you make better use of useState by constructing a custom hook:

function useUpdateUserAccount(updatedUserAccount) {
  const [userState, setUserState] = useState(null);

  useEffect(() => {
    function handleStatusChange(user) {
                        setUserState(user);
    }

    UserAPI.updateAccount(updatedUserAccount, handleUserChange);
    return () => {

    };
  }, []);

  return userState;
}

In this scenario, we now have a complete lifecycle of a function, thanks to additional hooks like useEffect. This can now be used as a custom hook across different components you may have. This could also even be a start of creating your own store rather than relying on Redux for simpler use-cases.

And don’t forget to add use as a prefix to your function name so you’re following the rules of hooks!

Passing setState function to child components for setting parent state

This is basically the same bad practice as passing useState in the child component. This time, we’re only passing the setState function to set the state of our parent component.

This is possible to do. But, it’s bad practice and may potentially introduce unintended side effects along the way as the app scales.

It’s also not easy to read and may cause confusion, especially when components become entangled with complicated use-cases.

So, instead of doing this:

const NewComponent = ({ setValFn }) => {

  return (<div>
    <button onClick={() => setValFn(25)}>Change value</button>
  </div>);
}

const App = () => {
  const [val, setVal] = useState(0);

  return (
    <div>
      <h2>Value: {val}</h2>
      <br/><br/>

      <NewComponent setValFn={setVal} />      
    </div>
  )
}

Do this:

const NewComponent = ({ onChangeValue }) => {

  return (<div>
    <button onClick={() => onChangeValue(25)}>Change value</button>
  </div>);
}

const App = () => {

  const [val, setVal] = useState(0);

  const onValueChanged = (input) => {
    setVal(input);
  }

  return (
    <div>
      <h2>Value: {val}</h2>
      <br/><br/>

      <NewComponent onChangeValue={onValueChanged} />      
    </div>
  )
}

You’re basically doing the same thing as before, where we intend to set a parent component’s state. Only this time, the latter approach is emitting events from child component to parent component. Then, you let the parent component do the setting of state.

Not using array destructure to use useState

You may not be aware of it, but you can use useState this way:

const count = useState[0];
const setCount = useState[1];

This is because hooks like useState are actually an array that returns the following implementations in each element:

  1. Initialized state value: the value you passed in its function. It can be a value, a string, an object, an array, etc.)
  2. Function to set your state

Official React docs prefer you to use array destructure instead because it’s cleaner and easier to read whenever you declare a state hook. Moreover, they use array destructuring, which suits their use-case of declaring the state elegantly.

This does not mean you’re using useState wrong, but not using ES6 destructure is taking away the syntactic sugar of how useState is meant to be declared, not to mention that you also added an extra line of code to declare them both.

We can see the official React documentation on how they prefer useState to be called using ES6 array destructure, like so:

const [count, setCount] = useState(0); //Array destructuring

Takeaway: Now at least you know you can use useState in an array fashion!

Relying only on useState for managing state in larger-scale applications

There should be no problem relying on useState for isolated cases in component logic and simple use-cases. But if our entire app only consists of useState for managing state, then we may have a problem in the long run due to the complexities and a use-case involving more than just two components.

Use-cases which needs more than just using useState include:

  1. If a state is needed in a couple of components
  2. If an app scales
  3. If we need a global store

If we solely rely on useState and pass the state to component props alone, we may end up with the “Prop Drilling” issue. Also, if we are going to add logic that’s related to authentication and security (which will be necessary to require holding the user session in a state at some point), then we’ll need a better state-management to properly store and use the logic across different pages that contain different components.

State-management libraries such as Redux or even Context API offer a significant advantage on larger-scale apps because they can share the state across different components. They often come along with browser tools to track what state is being passed in a couple of components.

This makes it easier to share and check the logic because of the sophisticated tools enabled by using state-management solutions such as Redux.

So, for larger-scale apps and state, which is needed in multiple components? Go for state-management such as Redux. But there are a couple of state-management solutions you can choose from, ranging from Flux or just Context API.

How about using custom hooks? Possible. But to be safe, it’s better to rely on Redux for bigger use-cases.

Conclusion

React is now flexible thanks to Hooks. Most especially, it’s thanks to useState that we no longer need to rely on class-based components just to construct our UI Components.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Sonny Recio 10% Entrepreneur | Fitness Junkie | Software Engineer | Fullstack web developer | Game Developer | Article writer and musician | Lifelong learner

9 Replies to “The noob’s guide to useState”

  1. Honestly, most of this article is pretty useless. Anyone who has read the “getting started” of hooks won’t do any of this. Only the part about not passing setValue to the children might be a real world case.

    It’s a bit preposterous to say that I’m using it wrong and then make a list of super obvious mistakes that honestly, the incredible minority of newbies would do.

    Clickbait article, wasted my time.

  2. Title should be “I was using use state wrong”
    It would be very hard to make some of those mistakes if you read the React documentation.

  3. Been working with hooks since they were released, reviewed thousands of PRs.
    Never saw even one of this cases.

  4. Not even that was realistic, why would someone use a prop to pass a function that can be easily imported.

  5. Yeah this article is pointless and clickbaity. I was like “oh what am I doing wrong?” and I thought it would be a best practice thing like don’t use too many useStates or use somethinge else. Then it was just like “don’t change the state directly” which is the first thing you learn.

  6. The first example isn’t really valid is it? You can’t reassign a constant, and that’s the point of declaring it that way.

Leave a Reply