Greetings, In this article, we will delve into a specific scenario and provide a solution for it. Now, let's take a look at the following code snippet:
import { useState } from "react";
import "./App.css";
import ChangeCounter from "./ChangeCounter";
function App() {
const [counter, setCounter] = useState(0);
const incrementCounter = () => {
setCounter(counter + 1);
};
const decrementCounter = () => {
setCounter(counter - 1);
};
return (
<div>
<ChangeCounter increment={incrementCounter} decrement={decrementCounter} />
{counter}
</div>
);
}
export default App;
In this block of code, there is a state called counter
and two functions incrementCounter
, decrementCounter
which are responsible for changing the counter
, these two functions are props of the ChangeCounter
component which has two buttons to increase and decrease the counter
, the ChangeCounter
component looks like this :
import React, { memo } from "react";
const ChangeCounter = ({ increment, decrement }) => {
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
};
export default memo(ChangeCounter);
This component is memoized using memo
which prevents unnecessary re-renders when the props remain unchanged. However, clicking the buttons will still trigger a re-render. What could be causing this issue?
The issue lies in the incrementCounter
and decrementCounter
declarations. Whenever the counter
is modified, the App
component will re-render, causing these two functions to be recreated. Due to having a different reference from the last props, the ChangeCounter
will also re-render! The key solution here is useCallback
which caches these two functions.
The code is being updated to the following:
import { useCallback, useState } from "react";
import "./App.css";
import ChangeCounter from "./ChangeCounter";
function App() {
const [counter, setCounter] = useState(0);
const incrementCounter = useCallback(() => {
setCounter(counter + 1);
},[]);
const decrementCounter = useCallback(() => {
setCounter(counter - 1);
},[]);
return (
<div>
<ChangeCounter increment={incrementCounter} decrement={decrementCounter} />
{counter}
</div>
);
}
export default App;
The ChangeCounter
will no longer undergo re-rendering. However, a new issue arises with the decrementCounter
and incrementCounter
functions as they are not functioning correctly. This is due to their body using the values from the initial render, which look like this:
const incrementCounter = () => {
setCounter(0 + 1);
};
const decrementCounter = () => {
setCounter(0 - 1);
};
These functions only assign the values 1 or -1 to the counter
!
One way to solve this is to include the counter
in the useCallback
dependency list. However, this approach makes decrementCounter
and incrementCounter
to be recreated and the ChangeCounter
will re-render again.
A better solution would be to use an updater function in the setCounter
. Using the updater function, the setCounter
will always update the previous value in the rendering queue. For more detailed information, you can refer to this link.
As a result, the updated version of the App
component should appear as follows:
import { useCallback , useState } from "react";
import "./App.css";
import ChangeCounter from "./ChangeCounter";
function App() {
const [counter, setCounter] = useState(0);
const incrementCounter = useCallback(() => {
setCounter(state=>state + 1);
},[]);
const decrementCounter = useCallback(() => {
setCounter(state=>state - 1);
},[]);
return (
<div>
<ChangeCounter increment={incrementCounter} decrement={decrementCounter} />
{counter}
</div>
);
}
export default App;
In the latest version, the setCounter
includes an updater function that uses the state from the previous render to update the counter
.
Thank you for taking the time to read this post. I suggest checking out the React official documentation for further insights on Hooks and React Apis.
Top comments (0)