DEV Community

Cover image for Use superstate instead of React.Context
Guilherme Oderdenge
Guilherme Oderdenge

Posted on

Use superstate instead of React.Context

Greetings, developers!

One highly popular way developers use to share state across components is through the React's Context API, and we can't deny it is useful and have been solving our problems for quite some time.

But let's take some time to pay attention to their own documentation:

Before you use Context

Context is primarily used when some data needs to be accessible by many components at different nesting levels. Apply it sparingly because it makes component reuse more difficult.

According to my perception however, one part that was completely skipped is the last one:

Apply it sparingly because it makes component reuse more difficult.

I've seen many projects—mine included—heavily relying on Context to share state. And that caused many problems; from developer experience decay to unmaintanable state management.

Just to name one problem, and very likely the most common one to me, what if you need to access a given state outside the React realm? Solving that isn't exactly straightforward and even creating a workaround solution is counterintuitive.

Let's pick another piece of what Context's docs itself says:

[...] when some data needs to be accessible by many components at different nesting levels.

Nesting is another point you have to worry about when using Context. In a large codebase, it's easy to get lost and not knowing why your state is not accurate—perhaps you're calling a Context at a level it isn't available; who knows?

These are some of the reasons why superstate exists. To make state management plain and crystal clear.

In practice

Using Context, in an app that has a dark/light theme, this is one way to achieve it:

import { createContext, useContext } from 'react'

export const ThemeContext = createContext({
  theme: 'light',
  setTheme: () => {}
})

export function App() {
  const [theme, setTheme] = useState('light')

  return (
    <ThemeContext.Provider
      value={{
        theme,
        setTheme: (newTheme) => setTheme(newTheme)
      }}
    >
      <Button />
    </ThemeContext.Provider>
  )
}

function Button() {
  const themeCtx = useContext(UserContext);  

  return (
    <button
      onClick={() => {
        themeCtx.setTheme(themeCtx.theme === 'dark' ? 'light' : ' dark')
      }}
    >
      Toggle theme
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now, with superstate:

import { superstate } from '@superstate/core'
import { useSuperState } from '@superstate/core'

export const theme = superstate('light')

export function App() {
  return <Button />
}

function Button() {
  useSuperState(theme)

  return (
    <button
      onClick={() => {
        theme.set(prev => prev === 'dark' ? 'light' : 'dark')
      }}
    >
      Toggle theme
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

As you can see from the example above, superstate has trimmed down the code required to achieve the same solution. That's not the pivotal point though; the graceful part is that you have a sleeker, more welcoming API that doesn't care about hierarchy or nesting, leading to a cleaner code and greater developer wellness all around. Also, have you noticed you didn't have to create a set method yourself? 🪄

That said, maybe in your next state it'd be worth considering superstate as an option for state management? I'm pretty sure you're going to like it.

Top comments (0)