Tricky Question for React interview
Tags: javascript, react, webdev, programming
Published: 2024-04-12 05:25:40

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](https://react.dev/learn/queueing-a-series-of-state-updates).

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](https://react.dev/reference/react/hooks) and React [Apis](https://react.dev/reference/react/apis).