It’s 2020 but I still see questions everywhere about “Which state management solutions should I use?” in React.

After 5 years of experience with React, my opinion is now summed up in this picture (click to view full size):

(opens new window)

Instead of seeking the perfect state management solutions for your use case, remember that as a project grows, its use cases tends to changes, seek to learn how to build well-factored software. The programming is terrible(opens new window) blog, The Architect Elevator(opens new window) website, and Martin Fowler(opens new window)’s website are filled with great advice. Then all the solutions out there will become tools that can be wielded effectively.

# Why do I recommend this?

I have observed that a single piece of data will be reused in multiple views. Let’s consider a chat application where you can mark certain chats as pinned.

Before React Query(opens new window) existed, I might use Redux(opens new window) to do this. So naturally, each view component would use useSelector(opens new window) and useDispatch(opens new window).

Fast forward to 2020, and now React Query(opens new window) is a thing. Now I am envy of all the greenfield apps that gets to use it.

Looking back, the original implementation we have its full of boilerplate and is bug-ridden. Sometimes the state would not synchronize correctly due to me forgetting to put useEffect in some places. The buggy code is then copy-and-pasted all over the place.

And so, it seems more effective to just switch to React Query. In this case I would use the useQuery(opens new window) and useMutation(opens new window) hooks instead. But then I would need to change several components. This is a laborious and boring task.

# In an alternative universe…

Now, what if, back at that time, I decided that “UI components should not be aware of what state management solution is being used”?

Then after the initial implementation using Redux, I would spend a few minutes harvesting(opens new window) the data-binding code and refactor it into a custom hook.

In the end, what defines a module is what pieces of the system it will never responsible for, rather what it is currently responsible for.

Tef, “Repeat yourself, do more than one thing, and rewrite everything”, programming is terrible(opens new window)

2020 comes and React Query becomes popular. In this alternate reality, the work required of me was much fourfold smaller. The UI components can be blissfully ignorant that behind their backs, the state management solution has been replaced.

# Decoupling UI from state management

Multiple techniques exist:

  • Hooks(opens new window) are generally the cleanest solution

    const pinnedChats = usePinnedChats()
    
  • Connector components (using render props(opens new window)) can help if you have a lot of class-based components. Class components cannot use hooks so in these cases you can create an adapter which lets class components access hooks through render props.

    function PinnedChatsConnector({ children }) {
      const pinnedChats = usePinnedChats()
      return children({ pinnedChats })
    }
    
  • Higher-order components(opens new window) are generally considered legacy. If your code have a lot of higher-order components and you are not ready to rewrite the whole component yet, you can, again, create an adaptor that allows connectors to be used as a HOC.

    function withPinnedChats(WrappedComponent) {
      return Object.assign(function WithPinnedChats(props) {
        return (
          <PinnedChatsConnector>
            {({ pinnedChats }) => (
              <WrappedComponent {...props} pinnedChats={pinnedChats} />
            )}
          </PinnedChatsConnector>
        )
      })
    }
    

In 2018, I talked about this idea with more examples in my talk Smells in React Apps(opens new window).