Optimizations in React part 1
Tags: javascript, webdev, react, performance
Published: 2022-04-14 19:24:59

Do we even need optimizations?

![Bounce rate credit: siteimprove.com](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkvt95u9zhgiv9zanhl1.PNG)

Seeing as most people leave a website within the first 5 seconds of nothing, we should make sure we are in a good spot, we can use **Lighthouse** to run a performance report from the inspect tab.

After looking at our website over at [Workiz.com](https://www.comeet.com/jobs/workiz/F6.006?coref=1.10.r87_B07&t=1650039595111)
we've noticed we had some place to improve so we decided to refactor some stuff and optimize some other stuff.

## Re-Rendering
Let's start at the beginning, when does a React component re-render?

1. When either the props or state change
2. When a parent component re-renders
3. When a hook changes

Let's take a look at the next component:
``` js
const Counter = () => {
    const initialCount = 
parseInt(window.localStorage.getItem("count") ?? "0");
    const [count, setCount] = useState(initialCount);
    const increment = () => {
        window.localStorage.setItem('count', count + 1);
        setCount(count + 1);
    }
    return (
      <>
        Count: {count}
        <button onClick={increment}>+</button>
      </>
    );
  }
``` 
We have a component that has some initial state **initialCount**, which it gets from the localStorage, and a function "increment" which increments the count by 1, and then stores that count in the localStorage.

For the sake of readability I will rename some of the functions 
``` js
const getCountFromLS = () => parseInt(window.localStorage.getItem("count") ?? "0");
const setCountToLS = (count) =>
window.localStorage.setItem('count', count);
```

``` js
const initialCount = getCountFromLS();
const [count, setCount] = useState(initialCount);
```
Every time we "increment", we notice that the initialCount is **being read again** from the localStorage even though we don't even use it beyond the first render.

Luckily for us **useState** accepts either **a value or a callback function**, meaning we can turn our initialCount into **a function that returns a value**, instead of just a value. 
We can store the function in the scope of the Counter and call it only from the initialization callback we receive from useState.
``` js
const initialCount = () => getCountFromLS();
const [count, setCount] = useState(()=>initialCount());
```
This is called **Lazy Initialization**, it's a very subtle change, but now we are actually giving useState a **function** instead of a **value**, it's the same as this:
``` js
const initialCount = () => getCountFromLS();
const [count, setCount] = useState(initialCount);
```
Now every time our component re-renders it doesn't affect the **initialCount** as it's now being called only once during the first initialization of the component and never again...

...unless someone moves away from our Counter.
In that case when they go back to it, we will need to **render the Counter for the first time again** and do another heavy IO operation (getting stuff from the localStorage is expensive).

Which is why we should probably **move the function call outside our component's life cycle**, we would generally want to bring all our data somewhere at the top of our application instead of when a component requires it.
``` js
const expensiveInputOperation = getCountFromLS();
const Counter = () => {
    const [count, setCount] = useState(expensiveInputOperation);
...
```
Now we are doing the expensive operation **outside** of our component's life cycle and just passing **the value** to useState.
Since "expensiveInputOperation" is a **constant** there is no need to use a callback function.

Now let's introduce a new component called **CoolButton**.
CoolButton is just a very basic button that does some really important calculation every time we click on it.

``` js
const CoolButton = ({ clickHandler }) => {
    const handler = () => {
        ReallyImportantCalculation();
        clickHandler();
    };
    return <button onClick={handler}></button>;
  };
```

Let's replace the button in our Counter with our new CoolButton:

``` js
const Counter = () => {
    const [count, setCount] = useState(expensiveInputOperation);
    const increment = () => {
        setCountToLS(count + 1);
        setCount(count + 1);
    }
    return (
      <>
        Count: {count}
        <CoolButton clickHandler={increment}>+</CoolButton>
      </>
    );
  }
```

Now we have a Counter which has a CoolButton inside it.
When we click the button we actually render both the Counter and the CoolButton even though **nothing changed in the CoolButton**. 

How do we stop this from happening?

### React.memo
Luckily for us, React gives us a way to counter the rendering of the parent by allowing the child to render at its own pace, and not rely on the renders of the parent.
This is the same as using **React.PureComponent** instead of a regular **React.Component**

``` js
const CoolButton = React.memo(({ clickHandler }) => {
    const handler = () => {
        ReallyImportantCalculation();
        clickHandler();
    };
    return <button onClick={handler}></button>;
  });
```

Now we click the button and everything works properly, but we still keep on re-rendering the **CoolButton**... 

Wasn't **memo** supposed to stop the re-renders? 

To understand why this is happening it's important to remember that React checks if the props or the state changed based on **shallow equality**.
This means that when memo encounters an object in its props, it **can't tell whether the objects are the same**.
``` js
{'test':true} == {'test':true} // FALSE
```
Javascript checks whether the **references** are the same and not if they have the **same values** inside them.
Going back to our component, what happened that caused the re-render?

Let's take a look at the parent component again:
``` js
const Counter = () => {
    const [count, setCount] = useState(expensiveInputOperation);
    const increment = () => {
        setCountToLS(count + 1);
        setCount(count + 1);
    }
    return (
      <>
        Count: {count}
        <CoolButton clickHandler={increment}>+</CoolButton>
      </>
    );
  }
```
Every time we click the button we render **Counter** again.

When we render Counter all the functions are being run again, which means we get a **new anonymous function** called "increment" every time.
We then pass this new "increment" to our CoolButton **as a prop**, meaning "increment" from a render ago is **not the same** "increment" as we have right now, so it's only natural to re-render our button again.

What can we do?

### React.useCallback

**useCallback** to the rescue!
This react hook ensures that we receive a reference to the function that will only change if **one of the dependencies in the square brackets change**, we can use this to **memoize** our "increment" function so that when Counter re-renders we will get **the same "increment"** and pass it to our CoolButton.

#### Attempt 1
``` js
const Counter = () => {
    const [count, setCount] = useState(expensiveInputOperation);
    const increment = useCallback(() => {
        setCountToLS(count + 1);
        setCount(count + 1);
    },[])
    return (
      <>
        Count: {count}
        <CoolButton clickHandler={increment}>+</CoolButton>
      </>
    );
  }
```

Ok cool so now we click the button, but it **doesn't work more than once**, why is that?
That's because our function **never changes**, so whichever value of count it received in the beginning, that's the same count it will have until it gets destroyed, meaning it will always be 0 :(

I guess we should just add our count to the dependencies array, right? 
Well...yes we can do that, but then we would get **a different "increment" every time count changes**... which means we will need to re-render our CoolButton as well... back to square 1.

#### Attempt 2
Luckily for us **setCount** actually receives a callback function just like our useState function, only this one gives us the previous value and expects us to give it the next one.

Meaning we can do something like this:
``` js
 const increment = useCallback(() => {
        setCountToLS(count + 1);
        setCount(prevCount => prevCount + 1);
    },[])
```
Cool so now we have our setCount use a **callback function**.

What about the localStorage?
It still receives the same count every time, how can we fix this? Well that's easy enough -
Let's just put that call **inside our setCount callback** as well:
``` js
 const increment = useCallback(() => {
        setCount(prevCount => {
        setCountToLS(prevCount + 1);
        return prevCount + 1;
        })
    },[])
```
And now everything works properly!

``` js
const CoolButton = React.memo(({ clickHandler }) => {
    const handler = () => {
        ReallyImportantCalculation();
        clickHandler();
    };
    return <button onClick={handler}></button>;
  });
const expensiveInputOperation = 
parseInt(window.localStorage.getItem("count") ?? "0");
const Counter = () => {
   const [count, setCount] = useState(expensiveInputOperation);
   const increment = useCallback(() => {
   setCount(prevCount => {
          window.localStorage.setItem("count", prevCount + 1);
          return prevCount + 1;
        });
    }, []);
   return (
      <>
        Count: {count}
        <CoolButton clickHandler={increment}>+</CoolButton>
      </>
      );
  }

```
If you're asking yourself why aren't we wrapping our "handler" function in a useCallback as well, we should remember that **memoization isn't free**.
Everything in programming is a **trade off**, you gain some but lose other, for memoization we would need to keep this data somewhere to use it later.
Primitive types like `<button>`, `<input>`, `<div>`, etc. are very cheap to render so we don't have to save all of them.
We should use these techniques only when we see an **impact on our user's experience**, for the most part React does a pretty good job even with the re-renders.

The next part will be about useMemo, stay tuned!