Tricky Question for React interview

Rmag Breaking News

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 incrementCounterto 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.

Leave a Reply

Your email address will not be published. Required fields are marked *