React: Docs - Hooks: is that `const` a typo ?

Created on 26 Oct 2018  路  12Comments  路  Source: facebook/react

The very first example of the hoks-intro shows the following code:

import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

but since that count is _theoretically_ (and visually) scoped within Example as const, I wonder how that is supposed to work (I mean, ever showing anything different from 0) or if there is a typo so that the first line should be instead:

let [count, setCount] = useState(0);

Thanks for clarifying and/or fixing that very first example.

Most helpful comment

@WebReflection After calling setCount the component is rerendered and the new call of useState returns the new value. The point is that count is immutable. So there's no typo here.

All 12 comments

@WebReflection After calling setCount the component is rerendered and the new call of useState returns the new value. The point is that count is immutable. So there's no typo here.

that's because you should not change count directly but only with setCount.

every rendereing count is result value of the memoized State for that func comp.

@TrySound thanks for the quick explanation, but I'm still a bit puzzled.

If I transpile that via Babel 7 I read this output:

import { useState } from "react";

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return React.createElement(
    "div",
    null,
    React.createElement("p", null, "You clicked ", count, " times"),
    React.createElement(
      "button",
      { onClick: () => setCount(count + 1) },
      "Click me"
    )
  );
}

It is not clear to me how setCount could interfere at all with that count variables, unless the transpiled output is not what hooks would generate, or unless there is some mapping of which part of that text should update accordingly with the new immutable value.

I can close this since I understand it's meant to be like that, but having an extra explanation of how it works behind the scene would help me.

Thanks.

@WebReflection

function useState(initialState) { return useReducer(basicStateReducer, initialState); }

it's a redux-like dispatching new value and returning newState
every rendering count is a new immutable state.

setCount => dispatch(newValue)
next Rendering
newCount = dispatch(newValue);

thanks @salvoravida , so ... if I understand correctly, the dispatcher is capable of updating that count "_slot_" only with the new value, right?

It's this "_mapping_" that I'm not fully following, but I guess I should just check internals.

Anyway, I'm definitively less puzzled now 馃憤

I think the idea is that setCount doesn't update the count variable but rather the the over arching state of the component. When the component re-renders the first argument is the current value held in the state for this "slot".

For example consider a useState implementation that is as trimmed down to:

var current = null
function setState () {
  this.setState({count: value})
}
function useState() {
  return [current.state.count, setState.bind(current)]
}

Where current is always a component instance before a component is invoked when React is traversing the tree. i.e

try {
  invokeComponentRenderFunction(vnode, current = vnode.instance, ...etc)
} finally {
  current = null
}

the idea is that setCount doesn't update the count variable but rather the the over arching state of the component

I got that, it's just not attached to anything so it's a bit of a mystery how that happens.

Without checking internals, all I can think is that useState returns something able to remember last value and/or update the previous one:

const useState = initial => {
  return [{valueOf: () => initial}, next => {
    initial = next;
  }];
};

// so that ...
const [count, setCount] = useState(0);

console.log(+count); // 0
setCount(count + 1);
console.log(+count); // 1

or some similar alchemy well hidden behind the component scene

You can implement useState in user-land entirely with the following:

var stack = []
var index = 0

function setState (value) {
  this.setState({value: value})
}

function useState (value) {
  try {
    const frame = stack[index]
    const state = frame.state
    const value = 'value' in state ? state.value : state.value = value
  } finally {
    return [value, setState.bind(frame)]
  }
}

function use (fn, sp = index++) {
  return class extends React.Component {
    state = {}
    render() {
      try {
        return fn.call(stack[index = sp] = this, this.props)
      } finally {
        stack[sp] = null
      }
    }
  }
}

const Example = use(function () {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
})

I guess what I was stupidly missing is that {count} is passed / set as count property while in a template literal it would've been just a number value without any attached reference.

I guess I got bitten by the fundamental difference between JSX (which I don't use) and template literals (which I use daily).

Mystery solved, thanks a lot for this last example!

edit ... actually no, I'm still a bit puzzled because one example checks if the constant has a different value than the one returned ... fascinating, I need a weekend 馃ぃ

Yes, the implementation had a bug in the setState call and doesn't handle multiple useState calls.

I may be entirely wrong and this may do more harm than good but as far as I understand it, the handwave-y explanation seems to go something like this:

Unlike what you may have been told to imagine, <Component/> is not really as straightforward as Component(). While the component function is at some point invoked and its return value is at some point applied to the component tree, there's this entire lifecycle thing going on and to do that React already keeps a lot of state around for each component.

As an aside, one example of that state is apparently the "local" state class-based components expose via this.state and this.setState. In other words, while they're exposed in the class as instance properties, they're not really "controlled" by the instance, which is why you're supposed to use this.setState (which btw may take effect asynchronously, which is why it provides a callback form if you need to access the current state to derive the new state).

Because the component function isn't invoked directly by the user but instead invoked "by React" (somehow), React knows in advance when a function will be invoked and thus can provide "global" functions (namely the use* hooks) that are aware of the current rendering context.

Because JS is single-threaded (as far as this part is concerned) and component functions always execute synchronously, hooks can be recognised by their execution order (assuming you follow the rules and always put them at the start of the function and never invoke them in conditionals).

So the reason you're getting a constant value (and why there's no point in modifying it even though you could) is that you're not actually receiving the "state object", you're receiving the state's current value, and a callback to assign a new value. The state itself lives outside the function (in the rendering context). That part takes some getting used to but it's how the state "persists" across multiple invocations of the component function for the same component instance (i.e. on rerenders).

tl;dr: useState hooks into the component tree's local context in which the component is rendered; the state it creates lives in that context, the function just returns a setter and the current value at the time the component function is invoked.

(if this turns out to be completely wrong, let's pretend I'm just following Cunningham's Law)

@pluma : skimmed your answer a bit, but yes, it appears to be entirely correct. Ultimately, React knows exactly which component it's rendering at any point in time, class or function, and has internal bookkeeping attached to that. Effects are stored as a linked list attached to the metadata for a given function component instance.

Was this page helpful?
0 / 5 - 0 ratings