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);
}
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);
}