State is often called local or encapsulated. It is not accessible to any component other than the one that owns it. 

Ivan: I see 'state' as a view-model

 

useState is a React hook that lets you add a state variable to your component.

const [state, setState ] = useState( initialState )

 

const [foo, setFoo ] = useState( 0 )
setFoo(oldValue => oldValue + 1)

 

 Usage

  • Adding state to a component
  • Updating state based on the previous state
  • Updating objects and arrays in state
  • Avoiding recreating the initial state
  • Resetting state with a key
  • Storing information from previous render

 


 Gotchas

I’ve updated the state, but logging gives me the old value

Note: setCount(count) runs on the next frame.

function handleClick() {
console.log(count); // 0

setCount(count + 1); // Request a re-render with 1

console.log(count); // Still 0!

setTimeout(() => {
console.log(count); // Also 0!
}, 5000);
}

Solution: set the new value to a variable.

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

I’ve updated the state, but the screen doesn’t update

Note: Immutable: Don't change the internal value of objects

obj.x = 10; // 🚩 Wrong: mutating existing object
setObj(obj); // 🚩 Doesn't do anything

 

Solution: create a new state

// ✅ Correct: creating a new object
  setObj({
     ...obj,
   x: 10
});

 


I’m getting an error: “Too many re-renders”

Note: don't call setBlah in the JSX - this triggers a re-render

// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>

// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>

// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>

 


My initializer or updater function runs twice

Note: In Strict Mode initialisers are run twice. Mutable code causes this.

 

setTodos(prevTodos => {
  // 🚩 Mistake: mutating state
  prevTodos.push(createTodo());
});

 

setTodos(prevTodos => {
  // ✅ Correct: replacing with new state
   return [...prevTodos, createTodo()];
});

 

function TodoList() {
// This component function will run twice for every render.
const [todos, setTodos] = useState(() => {

// This initializer function will run twice during initialization.
return createTodos();
});

function handleClick() {
setTodos(prevTodos => {
// This updater function will run twice for every click.
return [...prevTodos, createTodo()];
});
}

// ...

 


I’m trying to set state to a function, but it gets called instead

 You can’t put a function into state like this:

const [fn, setFn] = useState(someFunction);

function handleClick() {
  setFn(someOtherFunction);
}

 

Solution

const [fn, setFn] = useState(() => someFunction);

function handleClick() {
   setFn(() => someOtherFunction);
}
 
Reason
 
Because you’re passing a function, React assumes that someFunction is an initializer function, and that someOtherFunction is an updater function, so it tries to call them and store the result. To actually store a function, you have to put () => before them in both cases. Then React will store the functions you pass.
 
const [fn, setFn] = useState(() => someFunction);

function handleClick() {
setFn(() => someOtherFunction);
}