Everybody says don’t do it. Never mutate state directly, always call setState. Instead, always create new objects and arrays when you call setState, which is what we did above with the spread operator. Learn more about how to use the spread operator for immutable updates. So there you go: that’s why you shouldn’t mutate state, even if you immediately call setState. Optimized components might not re-render if you do, and the rendering bugs will be tricky to track down.
setMovies(result);
console.log(movies) // movies here will not be updated
so do it in useEffect
useEffect(() => {
// action on update of movies
}, [movies]);
https: //www.google.com/search?q=can+i+use+state+value+in+function+immediately+after+setting+it&oq=can+i+use+state+value+in+function+immediately+after+setting+it&aqs=chrome..69i57j69i64.10511j0j7&sourceid=chrome&ie=UTF-8
this.state.cart.push(item.id);
this.setState({
cart: this.state.cart
});
// renders like normal! maybe?
class ItemList extends React.PureComponent {
render() {
return (
<ul>
{this.props.items.map(item => <li key={item.id}>{item.value}</li>)}
</ul>
);
}
}
Instead, every time you want to update an array, you’ll want to pass a new array to your state setting function. To do that, you can create a new array from the original array in your state by calling its non-mutating methods like filter() and map(). Then you can set your state to the resulting new array. There are some things you can’t do with the spread syntax and non-mutating methods like map() and filter() alone. For example, you may want to reverse or sort an array. The JavaScript reverse() and sort() methods are mutating the original array, so you can’t use them directly. If you want to change some or all items of the array, you can use map() to create a new array. The function you will pass to map can decide what to do with each item, based on its data or its index (or both).
import { useState } from 'react';
let nextId = 0;
export default function List() {
const [name, setName] = useState('');
const [artists, setArtists] = useState([]);
return (
<>
<h1>Inspiring sculptors:</h1>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<button onClick={() => {
setName('');
artists.push({
id: nextId++,
name: name,
});
}}>Add</button>
<ul>
{artists.map(artist => (
<li key={artist.id}>{artist.name}</li>
))}
</ul>
</>
);
}
setArtists( // Replace the state [ // with a new array ...artists, // that contains all the old items { id: nextId++, name: name } // and one new item at the end ]);
import { useState } from 'react';
let nextId = 0;
export default function List() {
const [name, setName] = useState('');
const [artists, setArtists] = useState([]);
return (
<>
<h1>Inspiring sculptors:</h1>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<button onClick={() => {
setName('');
setArtists([
...artists,
{ id: nextId++, name: name }
]);
}}>Add</button>
<ul>
{artists.map(artist => (
<li key={artist.id}>{artist.name}</li>
))}
</ul>
</>
);
}
import { useState } from 'react';
let initialArtists = [
{ id: 0, name: 'Marta Colvin Andrade' },
{ id: 1, name: 'Lamidi Olonade Fakeye'},
{ id: 2, name: 'Louise Nevelson'},
];
export default function List() {
const [artists, setArtists] = useState(
initialArtists
);
return (
<>
<h1>Inspiring sculptors:</h1>
<ul>
{artists.map(artist => (
<li key={artist.id}>
{artist.name}{' '}
<button onClick={() => {
setArtists(
artists.filter(a =>
a.id !== artist.id
)
);
}}>
Delete
</button>
</li>
))}
</ul>
</>
);
}
setArtists(artists.filter(a => a.id !== artist.id));
If you’re coming from classes, you might be tempted to always call useState() once and put all state into a single object. You can do it if you’d like. Here is an example of a component that follows the mouse movement. We keep its position and size in the local state: Normally, you shouldn’t mutate local state in React. However, as an escape hatch, you can use an incrementing counter to force a re-render even if the state has not changed: constructor: Function components don’t need a constructor. You can initialize the state in the useState call. If computing the initial state is expensive, you can pass a function to useState.
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';import Counter from './Counter';
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('can render and update a counter', () => {
// Test first render and effect
act(() => { ReactDOM.createRoot(container).render(<Counter />); }); const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
// Test second render and effect
act(() => { button.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
});
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
function Box() {
const [state, setState] = useState({
left: 0,
top: 0,
width: 100,
height: 100
});
// ...
}
// ...
useEffect(() => {
function handleWindowMouseMove(e) {
// Spreading "...state" ensures we don't "lose" width and height setState(state => ({ ...state, left: e.pageX, top: e.pageY })); }
// Note: this implementation is a bit simplified
window.addEventListener('mousemove', handleWindowMouseMove);
return () => window.removeEventListener('mousemove', handleWindowMouseMove);
}, []);
// ...
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render. setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below. Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
render()
constructor(props)
constructor(props) {
super(props);
// Don't do this!
this.state = {
color: props.color
};
}
componentDidMount()
This pattern is commonly used to update state in class-based components, but this does not work with the useState hook. State updates with useState's setState function are not automatically merged. Because if we did not re-render upon updating state, we would not be able to show new data. This is very simply expressed, whenever we are showing any state contained within a state variable within our JSX. If it did not re-render whenever we make changes to that variable, the updates would not be shown.
A great advantage of the useState hook is that we are able to call it as many times as we like to use as many state variables as we need.
In this example, we have a basic form with an email and password input. We are managing the email and password state as individual state variables:
import React from "react";
export default function App() {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
return (
<form>
<input
name="email"
type="email"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
<input
name="password"
type="password"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
How do we appropriately update state with the setState
function when it is an object?
If we were to use a generic event handler that is connected to the onChange
prop of each of our form's inputs, it would look something like this:
import React from "react";
export default function App() {
const [state, setState] = React.useState({
email: '',
password: ''
})
function handleInputChange(e) {
setState({
[e.target.name]: e.target.value
})
}
return (
<form>
<input
name="email"
type="email"
onChange={handleInputChange}
/>
<input
name="password"
type="password"
onChange={handleInputChange}
/>
<button type="submit">Submit</button>
</form>
);
}
Since the previous state is not automatically merged into the new state object, we must manually merge our state object with its previous properties using the object spread operator:
import React from "react";
export default function App() {
const [state, setState] = React.useState({
email: '',
password: ''
})
function handleInputChange(e) {
setState({
// spread in previous state with object spread operator
...state,
[e.target.name]: e.target.value
})
}
return (
<form>
<input
name="email"
type="email"
onChange={handleInputChange}
/>
<input
name="password"
type="password"
onChange={handleInputChange}
/>
<button type="submit">Submit</button>
</form>
);
}
One other thing to note here is that there is technically a way to manage state without causing a re-render. We can do so with a hook that most people don't view as being a stateful React hook – useRef
.
useRef can be used to store any value on its .current
property. In other words, if we wanted to make a simple counter with useRef and update a count value that we stored on it, even if we update its value, it would not show the correct count after the initial render because doing so does not trigger a re-render:
import React from "react";
export default function App() {
const countRef = React.useRef(0);
function handleAddOne() {
countRef.current += 1;
}
return (
<>
<h1>Count: {countRef.current}</h1>
{/* clicking this will not change display count */}
<button onClick={handleAddOne}>+ 1</button>
</>
);
}
The other reason for this, aside from our React application not working properly, is that it violates a core principle of React. This is the concept of immutability.
State updates should always be immutable. This means we shouldn't make our own changes or mutate the data stored in our state variables. Doing so makes our state unpredictable and can cause unintended problems in our application that are hard to debug.
import React from 'react';
export default function App() {
const [count, setCount] = React.useState(0);
// Don't assign state to new (non-state) variables
const newCount = count;
// Don't directly mutate state
const countPlusOne = count + 1;
return (
<>
<h1>Count: {count}</h1>
</>
);
}
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass. The componentDidUpdate function is invoked immediately after a state update occurs. To avoid an infinite loop, you should always use a conditional statement to be sure that the previous state and the current state are not the same: Now that we’ve established that delaying reconciliation of updates requests in order to batch them is beneficial, there are also times when you need to wait on the updates to do something with the updated values. In the next section, we’ll see how to do that.
For example, consider the code below:
//React
const handleClick = () => {
setName("Amaka")
setAge(20)
setAddress("No 3 Rodeo drive")
}
The second parameter to setState()
is an optional callback function. This argument will be executed once setState()
is completed and the component is re-rendered. The callback function is guaranteed to run after the state update has been applied:
//React
handleSearch = (e) => {
this.setState({
searchTerm: e.target.value
}, () => {
// Do an API call with this.state.searchTerm
});
}
//React
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
// Do something here
}
}
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains. Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function. Why does calling react setState method not mutate the state immediately?
Using callback method with setState:
To check the updated state
value just after the setState
, use a callback method like this:
setState({
key: value
}, () => {
console.log('updated state value', this.state.key)
})
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app' />