Understanding UseState React Hook

I’m reading this freeCodeCamp article to try and understand the useState React Hook. The following code is included in the article. The part I’m confused about is- how does the computer know what the variable prevCount is if it’s not defined anywhere?

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>
        Change!
      </button>
    </div>
  );
}

It doesn’t yet. All it knows is that you have a function. Imagine if you wrote it this way:

const myIncrementCallbackFunction = prevCount => prevCount + 1;

and then:

<button onClick={() => setCount(myIncrementCallbackFunction)}>

When you defined the function, you said, “hey, if this function gets passed a parameter, I want it to be called ‘prevCount’. I don’t know what it was called before, but whatever comes in as that first parameter, within this function, I’m calling it ‘prevCount’.” And fortunately, when setCount receives that callback function, it takes it, executes it, and passes a parameter. I don’t know what it is called internally to the inner workings of useState and we don’t really care. All we know is that it will pass the current value into that callback function as the first parameter. And then the callback function will grab that first parameter and call it whatever it wants.

2 Likes

prevCount => prevCount + 1 is a “lambda” function. The prevCount is defined in-place.

What this means is that the whole function is passed into setCount. And setCount takes the hold function and uses that function to adjust the state.

It might be easier to think of the function is the more traditional format:

function (prevCount) {
  return prevCount + 1
}

setCount will call this function, and as a result prevCount is set to the current state, and it returns preCount + 1.

If this doesn’t make sense, it might help if you get used to the idea of a function being passed to another function in pure JS (without react).

For example:

function loggitWithTen(f) {
  console.log(f(10))
}

loggitWithTen(v => v + 5)

This will write 15 to the console. Does it make sense why?

1 Like

Thanks, this definitely helps! I guess another thing I’m confused about is that the entire function doesn’t take in any parameters.

() => setCount(myIncrementCallbackFunction)

Because it starts with () =>, it means the function doesn’t take any parameters, correct? So how is prevCount being passed into the function?

I see what you are asking now.

Indeed () => setCount(myIncrementCallbackFunction) has no parameters. However it doesn’t need any, because it’s job is to handle the click event. And unless you need extra information about the click event itself, you can happy handle the event with no other information than the fact a click occurred.

This function call setCount and passes in the function. The function at this stage is not being called, it is being passed around, like a variable to the setCount

Inside setCount something happens. What? Well it’s code you don’t control anymore, its the hook code that the React frameworks has provided for you. So what does React do with the function you passed in? It calls it with the current state.

This is finally where we get to prevCount => prevCount + 1, because this is the code it will call. And this code is where prevCount is defined.

Now this may still not make sense, even if you read it twice, and if it doesn’t, then I suggest you build up some intuition by studying functions e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions and practicing using them in all sorts of crazy ways.

For example write a function that takes 3 functions and then passes x through the first, second and third one. Do this using both arrow functions and traditional function() both anonymous and named. Use a debugger when running the code and console.log to see what is going on. Hopefully it will make sense then.

There is a tacit knowledge aspect to this, and practice and struggle might be needed to get it. I have suffered through this myself but in C#, but the concept luckily is similar in JS so I didn’t have to relearn. That’s the good thing about this you can reuse this idea of passing functions in most modern languages.

3 Likes

So setCount gets its argument from the framework (from state).

And the value of that state starts at 0 because of what was passed to useState initially.

So setCount gets its argument from the framework (from state).

setCount takes a function as a parameter. (It can also take a value, but let’s not worry about that.) setCount takes that function and passes it a value. That happens internal to setCount and you don’t see it. This is part of functional programing - passing functions around as parameters.

And the value of that state starts at 0 because of what was passed to useState initially.

Yes, because that is the initial value being passed to useState. If you’d done:

const [count, setCount] = useState(127);

it would start out at 127.

okay, interesting. I think I first learned to pass in a value with the useState hooks and only learned to pass in a function without registering that it’s a completely different kind of thing. Duhrrr…

I might be overthinking/staring at this code too much now but… why wouldn’t the following work?

<button onClick={setCount(myIncrementCallbackFunction)}>

It wouldn’t work because what this would do is call setCount(myIncrementCallbackFunction) at the point of rendering the button. (I imagine it would cause an infinite loop as this will trigger another render after each render). You want it to be called at the right time - i.e. when the button is clicked.

I can’t overstate how eye opening it is to play with theses things in the debugger of Chrome or Firefox dev tools. The learning rate per minute is 10x. Try the code you suggested and then see what happens, if it crashes doesn’t matter :slight_smile: you are not going to release that code.

Another thing you might want to try, purely as a learning exercise, is use JS instead of JSX. See https://reactjs.org/docs/react-without-jsx.html because if you do that, you will see what is happening at the JS level. I think JSX is making it less clear in this situation.

1 Like

Write, what martinc said.

This is a common pattern in JS where you have a callback. If your callback takes no parameters, or takes the ones being sent by default by the element, then you can just give the function name:

<button onClick={ myCallbackFunction }>My Button</button>

But if you need to pass a parameter, you can’t just add the parameter:

// this won't work
<button onClick={ myCallbackFunction(myParameter) }>My Button</button> 

In the first case we were giving it the name of the function, passing in the reference or the address. In the second case we are actually calling the function at compile time and passing the return value to the onPress. We don’t want that, so we can wrap it in an anonymous function:

<button onClick={ <button onClick={ () => myCallbackFunction(myParameter) }>My Button</button> }>My Button</button>

Now, we are passing it an function again: () => myCallbackFunction(myParameter). When the click is done, that is the function that will get executed and it will call our callback with the parameter.

This is a patter that pops up a lot with callback functions.

There is also a pattern using currying where you can execute a function immediately there, but let’s not worry about that right now. I just mention it for completeness. It would just be a function that returns the function that we want.

1 Like