← Back to list

React Hooks at depth: useState()

Part 1: diving into useState()

Abdo Amin in JavaScript in Plain English · 2023-03-20 13:42 · 14 claps · 3.5 min read
#reactjs #react-hook #javascript #front-end-development
Open on Medium ↗
Wiki topics: 🌐 · Web Development

React Hooks at depth: useState()

Part 1: diving into useState()

I am starting a series of articles to share my humble experience using React hooks.

Playground: https://stackblitz.com/edit/react-ouuecu?file=src/App.js

React hooks

React hooks

I will start first with useState, where I will talk about three points:

  • useState is asynchronous
  • setting state with callbacks
  • Object vs atomic state

useState is asynchronous

I initially wrote an article before talking about my findings with regard to how useState behaves and how it doesn’t behave in an instant/synchronous way. Instead, when you setState with a new value, the hook triggers a component re-render, and the state will be updated on the new render.

Let’s write a pseudo code for the above.

Try to run the code below and you will notice that the count is still 0, then after a second, 1 will be logged.

const [count, setCount] = useState(0);
setCount(1);
console.log(count); // 0

The reason is (the coming is my assumption, not the real reason): the engine queued a component recycle*, then continued processing the rest of the component code, and then after finishing all the operations (functions) on the stack trace, the event loop pushed the component re-render operation to the stack trace and executed it, thus updating the component and the state.

*(async operation are queued to the side till sync functions are done, thus they are non-blocking…More on this will be explained in the useEffect article)

Update useState with callbacks

I took inspiration from this article, feel free to read it.

Look at the code below, try to run it, and let me know what you get.

  const [count, setCount] = React.useState(0);

  const increment = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    console.log(count);
  };
  return (
    <div>
      <button onClick={increment}>increment</button>
    </div>
  )

The output should look like this after 3 clicks:

// 0
----
// 1
----
// 2

Now, try to run the code below:

  const [count, setCount] = React.useState(0);
  const increment = () => {
    setCount(current => current + 1);
    setCount(current => current + 1);
    setCount(current => current + 1);
    console.log(count);
  };
  return (
    <div>
      <button onClick={increment}>increment</button>
    </div>
  )

The output should look like this after 3 clicks:


// 0
----
// 3
----
// 9

Teases your brain, right?

In the first code sample, where we used setCount(count + 1) , we used the count state which was initialized as 0, when we execute the increment function, what happened is we are incrementing the count state 3 times, but the thing is the count hadn’t been updated yet…So, think of it like so:

setCount(count + 1) // count is 0, so the result is 1
setCount(count + 1) // count is still 0, so the result is 1
setCount(count + 1) // count is also still 0, so the result is 1

However, when we updated the state with callbacks, like below:

setCount(function(currentState){
  return currentState + 1
})
// or
setCount(currentState => currentState + 1)

What is happening is when we provide a callback function to setState, we get an argument of the current state…Although the count state is not updated in the component scope, the count state is updated in the setState scope.

This is a cool trick because you now don’t need the state to setState, here is an example of a useState that only has a setState:

const [, setCount] = useState(0);

const increment = () => {
  setCount(curr => curr + 1)
}

Object vs Atomic State

While useState and hooks, in general, were built on the idea of functional programming and the premise of keeping things small and “atomic” like below:

const [username, setUsername] = useState("")
const [email, setEmail] = useState("")
const [firstName, setFirstName] = useState("")
// ...

While this is easier to read and work with but if you have to pass the state down to child components, you would end up with “props hell” like below:

This is why I prefer using object state which solves the problem above and is more concise and readable.

const Parent = () => {
  const [user, setUser] = useState({
  username: "",
  email: "",
  firstName:"",
  lastName:""
  })

  return ( <UserInfoComponent user={user} setUser={setUser} />);
}

// UserInfoComponent.jsx

const UserInfoComponent = ({user, setUser}) => {
  return (
    <input
    type="text"
    value={user.firstName}
    onChange ={e => setUser(prev => ({...prev, firstName: e.target.value})}
    // or onChange={e => setUser({ ...user, firstName: e.target.value})}
    />
  );
}

I only use this approach if all my object’s values are primitive, and not nested objects or arrays to keep things simple.

Thank you for reading.

More content at **PlainEnglish.io**.

Sign up for our **free weekly newsletter. Follow us on [Twitter](https://twitter.com/inPlainEngHQ)**, ***LinkedIn, [YouTube](https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw), and [Discord](https://discord.gg/GtDtUAvyhW).***

Interested in scaling your software startup? Check out **Circuit**.


메타데이터
post_id
56832320208
slug
react-hooks-part-one-usestate-56832320208
url
https://javascript.plainenglish.io/react-hooks-part-one-usestate-56832320208
canonical_url
https://javascript.plainenglish.io/react-hooks-part-one-usestate-56832320208
author_url
https://medium.com/@abdoamin
status
ok
fetched_at
2026-06-16 19:09:56