React Tips

React Tips

React is nowadays taking a big part of my development life as I lead the frontend team in my work place. I always encounter small tips when searching for solutions and I never remember them. I decided to store this information in this blog post rather then in some private doc so other people can benefit from this.

Hooks – useState

Merge Previous State

The useState hook doesn’t do merge with previous state as setState does in class components. If you initialize state with:

const [state, setState] = useState({
    hi: 'bye',
    yo: 'dawg'
});

and you perform

setState({ yo: 'dude' });

state will become

{
  yo: 'dude'
}

instead of

{
  hi: 'bye',
  yo: 'dude'
}

You can do merge by yourself with the spread operator as here:

setState(prevState => ({ …prevState, …{ yo: 'dude' } }));

Function state updater

Previous section leads to the question why there are 2 way to update state, by passing a value as first prop or by passing a function. Consider the following classic counter component, but this time I want to hack my counter to increase by 2:

const CharacterCounter = () => {
  const [count, setCount] = useState(0);
  
  const increaseBy2 = () => {
    setCount(count + 1);
    setCount(count + 1);
  };
  
  return (
    <div>
      <div>count: {count}</div>
      <button onClick={increaseBy2}>++</button>
    </div>
  );
};

You can run the code in here. Button click increment the counter only by one at a time. Count is only calculated once when we call useState and will be recalculated only the next render, so we basically pass the same value to the updater function that will actually update count only in the next render. It will be clearer if we change the increaseBy2 function to something like this:

  const increaseBy2 = () => {
    const next = count + 1;
    setCount(next + 1);
    setCount(next + 1);
  };

Another use case could be if your value is captured in a closure, and will be ‘cached’ with initial value. So to avoid this, it is a good practice, in my opinion, whenever you have update state function and it depends on previous state, pass update function.

const CharacterCounter = () => {
  const [count, setCount] = useState(0);
  
  const increaseBy2 = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
  };
  
  return (
    <div>
      <div>count: {count}</div>
      <button onClick={increaseBy2}>++</button>
    </div>
  );
};

Link to a working example.

React Context API

Using React Context as a global state manager

I was so enthusiastic with React Context API when I first used it so I had some great ideas how to make my applications use it as a global state manager. With the release of hooks API it was even more exciting and I ran to build my own state manager based on some ideas I had and read about across the web. Mainly I was inspired by this blog post. I added some more abstraction from my side to make the API look something like I used before and liked very much, vuex as part of my experience with VueJS.

Everything looked great until I opened the react dev tool. The whole application was re-rendering with every change in my global state. That is something that of course can lead to performance issue as application grows, and even though the application I built was pretty small, I couldn’t sleep thinking about those unnecessary renders. So 3 blog post made me think differently about small-mid application state management and I suggest you to read them:

  1. Global state with React
  2. Application State Management with React

What I realised after reading those blog posts is that context API is not a solution for managing a large-whole-app global state. Even when I tried to use useReducer, useCallback, useMemo and other cache methods it was not what I wanted. It always ended with dealing with the fact that changing context will re-render all parts using eventually my custom wrapper hook for useContext and useReducer. The final needle was when I read that react-redux project had performance issues with context based state management.

Finally what I decided for now is still to use React’s context API but more closely to the component that needs it, and even then, consider carefully if you even need it. I am still investigating how to get to the perfect spot of managing a small-mid size application without any 3rd party library which always add a lot of boiler plate, I will update for sure.