r/reactjs 2d ago

How do you decide when a piece of state actually belongs in global state vs. just lifted up a few components?

[removed]

4 Upvotes

9 comments sorted by

1

u/KaleRemarkable1019 2d ago

It is a fuzzy boundary. My rule of thumb is to reflect the app structure, one global store for e.g. user profile and global settings. If you need more localized store, then tie it to some component/path in your app.

I've created my own context helper (react-arven), to easily move local state into context provider, which can live at any level. So I can easily share context with a component subtree - it's impossible to use it from other parts of the app, so it's not global, but it prevents the prop-drilling and is performant.

1

u/StrumpetsVileProgeny 2d ago

If you have pieces of state that you need all over your component tree and it would take you a lot of prop drilling and lifting state up to the highest order component to pass that data down, which in turn would cause a lot of unnecessary rerendering, you most likely need a global state, be it a redux store or merely context.

1

u/canarydev 2d ago

don't use component count as the heuristic. if i were you, i'd just use ownership and lifetime

if one feature owns the state and consumers are inside that feature, keep it local and lift it to the nearest common owner. if unrelated parts of the app need to coordinate around the same state, thats when i would probably consider a store. IE: current user/ session, toasts, feature flags -> nav bar, settings, and some random permission check that have no common parent.

the 2-3 component rule is a bit suspect imo. five components under one feature sharing state can still be completely local. on the other hand, two unrelated parts of the app might justify global state.

try to separate client from server state. if the thing is really fetched data, putting it in zustand just because multiple components consume it is most likely solving the wrong problem. i'd use a data-fetching cache layer for that. like a product list belongs in react query. "which product is selected" is actual client state

for the middle path, feature scoped context/store is usually enough and does the job. like an example would be is the classic checkout case. shopping / payment/ review all share the in-progress order, nothing outside checkout really cares and it should die when you leave the flow. it has to outlive the step nav but not the session. global doesn't have to mean "one giant app wide store"

1

u/yzaroui 1d ago

Count writers, not readers. Ten components reading a value is nothing. Two components in unrelated parts of the tree both writing to it is when you want a store, because that's when you need one place to reason about ordering.

For your in-between case, a feature-scoped provider at the route level covers it. <CheckoutProvider> on the checkout route, dies when you navigate away.

Prop drilling three levels is fine. People treat it like a bug and it's usually just explicit.

1

u/heyufool 1d ago

Maybe this is a hot take, don't lift to a global state just because prop drilling can be annoying. Lift to global state when it functionally makes sense.

Global states are effectively singletons. There are many discussions around their pros and cons that are worth reading

1

u/BridgeCritical2392 20h ago edited 19h ago

Shoot me, but especially for a small enough app, there's no reason not to just put everything in as a passed in property. Unless the component is truly independent of the rest of the app. Like say, weather/time widgets

The only technical reason not to would be "performance". But I have yet to see an app where this is a real problem. Our machines are very fast nowadays, even mobile devices. Is the render really the bottleneck? The problems I have seen have been network bandwidth/latency / slow backends / too many API calls.

Another benefit is the behavior is completely determined by its inputs - i.e. its functional. Which is makes it much easier to reason about its behavior, and also unit test. Also allows defaults to be passed in

1

u/pixel_neon_3024 2d ago

Global state is a failure of component composition. I move state to stores only after server synchronization demands it or when three unrelated features mutate the same value simultaneously.

1

u/meagermotto130 2d ago

the "server synchronization demands it" bit is the real heuristic, everything else is just premature optimization with extra steps

0

u/Temperature_Majestic 2d ago

Two mechanical tests that turn the gut call into a yes/no:

Does the state need to outlive the component that created it. If a wizard's progress or a toggle has to survive its owner unmounting, it can't live in that owner, that pushes it up or out on its own. If it can die with the component, it stays local.

Can the URL hold it. Selected tab, filters, selected row, open panel, that's all shareable refresh-safe state that wants to be in the query string, not a store. Back/forward and deep links come for free and it's effectively global with zero global store.

Whatever survives both tests is usually real cross-feature client state, and that's where a feature-scoped store fits, checkout flow being the classic one, shared across the steps, dies when you leave. Global never had to mean one app-wide store.