useState hook
Abdulrahman Ismail
Posted on February 4, 2024
*useState * is a React Hook that lets you add a state variable to your component.
State: A Component's Memory
Components often need to change what’s on the screen as a result of an interaction. Typing into the form should update the input field, clicking “next” on an image carousel should change which image is displayed, clicking “buy” should put a product in the shopping cart. Components need to “remember” things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called state.
This is the form:
const [state, setState] = useState(initialState)
the useState hook return two values the initial value which we can use it and a in that case (state),which It can be a value of any type, but there is a special behavior for functions. This argument is ignored ** after the initial render(it mean it only render at the very first time). and setter function to modify the initial state
as you see we are using array **destructuring.
If you pass a function as initialState, it will be treated as an initializer function. It should be pure, should take no arguments, and should return a value of any type. React will call your initializer function when initializing the component, and store its return value as the initial state
this is an example:
import { useState } from 'react';
function createInitialTodos() {
const initialTodos = [];
for (let i = 0; i < 50; i++) {
initialTodos.push({
id: i,
text: 'Item ' + (i + 1)
});
}
return initialTodos;
}
export default function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
const [text, setText] = useState('');
return (
<>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
setTodos([{
id: todos.length,
text: text
}, ...todos]);
}}>Add</button>
<ul>
{todos.map(item => (
<li key={item.id}>
{item.text}
</li>
))}
</ul>
</>
);
}
This example passes the initializer function, so the createInitialTodos function only runs during initialization. It does not run when component re-renders, such as when you type into the input.
about the setter function of a useState:
You can pass the next state directly, or a function that calculates it from the previous state.
like:
import { useState } from "react";
import "./App.css";
function App() {
const [counter, setCounter] = useState(0);
const handleIncremeant = () => {
setCounter(counter + 1);
};
return (
<div className="App">
<header className="App-header">
<button onClick={handleIncremeant}>+1</button>
<p>{counter}</p>
</header>
</div>
);
}
export default App;
or
import { useState } from "react";
import "./App.css";
function App() {
const [counter, setCounter] = useState(0);
const handleIncremeant = () => {
setCounter((prevCounter) => prevCounter + 1);
};
return (
<div className="App">
<header className="App-header">
<button onClick={handleIncremeant}>+1</button>
<p>{counter}</p>
</header>
</div>
);
}
export default App;
but what is the difference you may ask?🤔
well the different is If you pass a function as nextState, it will be treated as an updater function. It must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying all of the queued updaters to the previous state. but what is that mean?
lets take you to the practical learning:
if we modify our code to this :
import { useState } from "react";
import "./App.css";
function App() {
const [counter, setCounter] = useState(0);
const handleIncremeant = () => {
** setCounter(counter + 1);
setCounter(counter + 1);
setCounter(counter + 1);**
};
return (
<div className="App">
<header className="App-header">
<button onClick={handleIncremeant}>+1</button>
<p>{counter}</p>
</header>
</div>
);
}
export default App;
what is the output of the counter? 1 or 3? and why?
after one click, +1 will only be 1 rather than 3! This is because calling the set function does not update the counter state variable in the already running code. So each setCounter(counter + 1) call becomes setCounter(1).
To solve this problem, you may pass an updater function to setCounter instead of the next state:
const handleIncremeant = () => {
setCounter((prevCounter) => prevCounter + 1);
setCounter((prevCounter) => prevCounter + 1);
setCounter((prevCounter) => prevCounter + 1);
};
in that modification:
prevCounter => prevCounter + 1 will receive 0 as the pending state and return 1 as the next state.
prevCounter => prevCounter + 1 will receive 1 as the pending state and return 2 as the next state.
prevCounter => prevCounter + 1 will receive 2 as the pending state and return 3 as the next state.
Call *useState * at the TOP level of your component
import { useState } from "react";
import "./App.css";
function App() {
//TOP LEVEL
** const [counter, setCounter] = useState(0);**
return (
<div className="App">
<header className="App-header">
<p>{counter}</p>
</header>
</div>
);
}
export default App;
if you don't call your state veriable at the top level of your components it'll give you an error like:
import { useState } from "react";
import "./App.css";
function App() {
const [counter, setCounter] = useState(0);
if (counter) {
counter.forEach((element) => {
const [people, setPeople] = useState(counter);
});
}
return (
<div className="App">
<header className="App-header">
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
</header>
</div>
);
}
export default App;
in that case we get this error
React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
we can't use it inside of a condition or loop, if in any case you need that extract a new component and move the state into it.
Updating objects and arrays in state
In React, state is considered read-only, so you should replace it rather than mutate your existing objects. For example, if you have a form object in state, don’t mutate it
// 🚩 Don't mutate an object in state like this:
form.firstName = 'Taylor';
Instead, replace the whole object by creating a new one:
// ✅ Replace state with a new object
setForm({
...form,
firstName: 'Taylor'
});
In this example, the form state variable holds an object. Each input has a change handler that calls setForm with the next state of the entire form. The { ...form } spread syntax ensures that the state object is replaced rather than mutated.
import { useState } from 'react';
export default function Form() {
const [form, setForm] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com',
});
return (
<>
<label>
First name:
<input
value={form.firstName}
onChange={e => {
setForm({
...form,
firstName: e.target.value
});
}}
/>
</label>
<label>
Last name:
<input
value={form.lastName}
onChange={e => {
setForm({
...form,
lastName: e.target.value
});
}}
/>
</label>
<label>
Email:
<input
value={form.email}
onChange={e => {
setForm({
...form,
email: e.target.value
});
}}
/>
</label>
<p>
{form.firstName}{' '}
{form.lastName}{' '}
({form.email})
</p>
</>
);
}
Resetting state with a key
You’ll often encounter the key attribute when rendering lists. However, it also serves another purpose.
You can reset a component’s state by passing a different key to a component. In this example, the Reset button changes the version state variable, which we pass as a key to the Form. When the key changes, React re-creates the Form component (and all of its children) from scratch, so its state gets reset.
Read preserving and resetting state to learn more.(this link is very important to learn where state live and how state works so definitely you need to check it out!)
import { useState } from 'react';
export default function App() {
const [version, setVersion] = useState(0);
function handleReset() {
setVersion(version + 1);
}
return (
<>
<button onClick={handleReset}>Reset</button>
<Form key={version} />
</>
);
}
function Form() {
const [name, setName] = useState('Taylor');
return (
<>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<p>Hello, {name}.</p>
</>
);
}
And Finally there are some troubleshooting:
I’ve updated the state, but logging gives me the old value
Calling the set function does not change state in the running code:
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);
}
This is because states behaves like a snapshot. Updating state requests another render with the new state value, but does not affect the count JavaScript variable in your already-running event handler.
I’m getting an error: “Too many re-renders”
You might get an error that says: Too many re-renders. React limits the number of renders to prevent an infinite loop. Typically, this means that you’re unconditionally setting state during render, so your component enters a loop: render, set state (which causes a render), render, set state (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:
// 🚩 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>
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);
}
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);
}
Posted on February 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024