useState
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.
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.
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.
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.
useState
in children component props to use itPassing 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.
useState
at the top of component body or functionsAccording 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.
useState
in class components or regular JavaScript functionsIf 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!
setState
function to child components for setting parent stateThis 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.
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:
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!
useState
for managing state in larger-scale applicationsThere 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:
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDesign React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.
10 Replies to "The noob’s guide to <code>useState</code>"
I was not using useState wrong.
Same here.
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.
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.
Been working with hooks since they were released, reviewed thousands of PRs.
Never saw even one of this cases.
Not even that was realistic, why would someone use a prop to pass a function that can be easily imported.
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.
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.
Only valid point is passing setState to children as props
The point displayed by example in the section “Not putting useState at the top of component body or functions” is wrong.
By top level the React documentation does not mean the line number but the nesting of hooks inside other elements. Both given examples are completely valid.
As long as your hook is on top level you can put in any working location of your component.