React follows Unidirectional Data Flow. Meaning, the data flow inside react should and will be expected to be in a circular path. I understand that React tutorials and documentation warn in no uncertain terms that state should not be directly mutated and that everything should go through setState. It surprises me that non of the current answers talk about pure/memo components (React.PureComponent or React.memo). These components only re-render when a change in one of the props is detected.
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
And another downside of mutation of Objects
and Arrays
in JavaScript is, when you assign an object or an array, you're just making a reference of that object or that array. When you mutate them, all the reference to that object or that array will be affected. React handles this in a intelligent way in the background and simply give us an API to make it work.
Most common errors done when handling states in React
// original state
this.state = {
a: [1, 2, 3, 4, 5]
}
// changing the state in react
// need to add '6' in the array
// bad approach
const b = this.state.a.push(6)
this.setState({
a: b
})
In the above example, this.state.a.push(6)
will mutate the state directly. Assigning it to another variable and calling setState
is same as what's shown below. As we mutated the state anyway, there's no point assigning it to another variable and calling setState
with that variable.
// same as
this.state.a.push(6)
this.setState({})
Now, mutating currentStateCopy
won't mutate the original state. Do operations over currentStateCopy
and set it as the new state using setState()
.
currentStateCopy.push(6)
this.setState({
a: currentStateCopy
})
Mutating state directly can lead to odd bugs, and components that are hard to optimize. Here’s an example. 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. Now, here is a tiny app that renders the ItemList and allows you to add items to the list – the good way (immutably), and the bad way (by mutating state). Watch what happens. Here is a simple component that renders a list of items (notice that it extends React.PureComponent):
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>
);
}
}
class App extends Component {
// Initialize items to an empty array
state = {
items: []
};
// Initialize a counter that will increment
// for each item ID
nextItemId = 0;
makeItem() {
// Create a new ID and use
// a random number as the value
return {
id: this.nextItemId++,
value: Math.random()
};
}
// The Right Way:
// copy the existing items and add a new one
addItemImmutably = () => {
this.setState({
items: [...this.state.items, this.makeItem()]
});
};
// The Wrong Way:
// mutate items and set it back
addItemMutably = () => {
this.state.items.push(this.makeItem());
this.setState({ items: this.state.items });
};
render() {
return (
<div>
<button onClick={this.addItemImmutably}>
Add item immutably (good)
</button>
<button onClick={this.addItemMutably}>Add item mutably (bad)</button>
<ItemList items={this.state.items} />
</div>
);
}
}
Despite React’s popularity, one of its biggest drawbacks is its components re-rendering excessively. When developing React applications, you may have noticed that state updates don’t immediately reflect new values after being changed. React state is a plain JavaScript object that holds information that influences the output of a render. 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: In this article, we’ll explore the reasons why React doesn’t update state immediately. We’ll run through an example and clarify what you should do when you need to make changes to the new state in both class and function components. Let’s get started!
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.
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
}
}
Next, we’ll make the Clock set up its own timer and update itself every second. Finally, we will implement a method called tick() that the Clock component will run every second. However, it misses a crucial requirement: the fact that the Clock sets up a timer and updates the UI every second should be an implementation detail of the Clock. To show that all components are truly isolated, we can create an App component that renders three <Clock>s:
const root = ReactDOM.createRoot(document.getElementById('root'));
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);}
setInterval(tick, 1000);
const root = ReactDOM.createRoot(document.getElementById('root'));
function Clock(props) {
return (
<div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> );
}
function tick() {
root.render(<Clock date={new Date()} />);}
setInterval(tick, 1000);
root.render(
<Clock />);
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div>
);
}
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()}; }
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
states are a data store which contains the data of a component. To make this happen, React provides setState() function which takes in an object of new states and does a compare and merge(similar to object.assign()) over the previous state and adds the new state to the state data store. setState trigger re rendering of the components.when we want to update state again and again we must need to setState otherwise it doesn't work correctly.
To avoid every time to create a copy of this.state.element
you can use update with $set or $push
or many others from immutability-helper
e.g.:
import update from 'immutability-helper';
const newData = update(myData, {
x: {
y: {
z: {
$set: 7
}
}
},
a: {
b: {
$push: [9]
}
}
});
class App extends React.Component {
state = { some: { rather: { deeply: { nested: { stuff: 1 } } } } };
mutatingIncrement = () => {
this.state.some.rather.deeply.nested.stuff++;
this.setState({});
}
nonMutatingIncrement = () => {
this.setState(R.evolve(
{ some: { rather: { deeply: { nested: { stuff: n => n + 1 } } } } }
));
}
render() {
return (
<div>
Normal Component: <CounterDisplay {...this.state} />
<br />
Pure Component: <PureCounterDisplay {...this.state} />
<br />
<button onClick={this.mutatingIncrement}>mutating increment</button>
<button onClick={this.nonMutatingIncrement}>non-mutating increment</button>
</div>
);
}
}
const CounterDisplay = (props) => (
<React.Fragment>
Counter value: {props.some.rather.deeply.nested.stuff}
</React.Fragment>
);
const PureCounterDisplay = React.memo(CounterDisplay);
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/ramda@0/dist/ramda.min.js"></script>
<div id="root"></div>
In the above example, this.state.a.push(6)
will mutate the state directly. Assigning it to another variable and calling setState
is same as what's shown below. As we mutated the state anyway, there's no point assigning it to another variable and calling setState
with that variable.
// same as
this.state.a.push(6)
this.setState({})
So, what's the best way to handle states in React? Let me explain.
When you need to change 'something' in the existing state, first get a copy of that 'something' from the current state.
// original state
this.state = {
a: [1, 2, 3, 4, 5]
}
// changing the state in react
// need to add '6' in the array
// create a copy of this.state.a
// you can use ES6's destructuring or loadash's _.clone()
const currentStateCopy = [...this.state.a]
How do we appropriately update state with the setState function when it is an object? This seems like a rather simple concept, but you need to understand that whenever we update state, it not only causes a re-render in the component that directly manages the state – it also causes a re-render in all child components. This can be seen if we take a look at the React documentation and see exactly what happens when we call the setState function. We use it to update the state variable associated with it, but we're also told:
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>
);
}
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>
</>
);
}