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>
)
}
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>
)
}
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)