Prompt of the Day: Replace Prop Drilling with Proper State Management
Part 24 of 30 in the Prompt of the Day series
Prop drilling happens when you pass data through three, four, five levels of components just to get it to the one component that actually needs it. Every component in between becomes a reluctant courier, accepting props it will never use just to hand them off. The problem is not just aesthetic -- intermediate components re-render when those props change, your component signatures bloat, and a single data shape change forces you to touch half a dozen files. In 2025, state management comparisons consistently show that most teams reach for the wrong solution because they apply it too broadly or not broadly enough. The fix is knowing when to extract state and exactly what to extract it into.
The Prompt
I have a React component tree where props are being drilled through 3 or more levels. Please help me refactor it.
Here is the relevant component tree:
[PASTE YOUR COMPONENTS HERE]
Do the following:
1. Identify every prop that is passed through a component that does not consume it (only forwards it).
2. For each drilled prop, determine the right solution:
- React Context: if the value changes infrequently and the subtree is self-contained (e.g., theme, locale, current user)
- Zustand: if the state is shared across unrelated parts of the app, updates frequently, or needs middleware like persistence
- Jotai: if the state is atomic and fine-grained, each piece updates independently, or you need derived/computed atoms
3. Refactor the component tree to eliminate the drilling using the chosen solution.
4. Preserve all existing TypeScript types and interfaces. Do not widen or loosen any type.
5. Do NOT move props into global state if a component genuinely owns them and passes them to direct children only. Call out which props you are leaving as props and why.
6. Show the before and after for each affected component.
Why It Works
This prompt is specific enough to prevent the AI from making sweeping decisions. The three-level threshold is explicit, so the model does not over-extract props from shallow trees where drilling is perfectly fine. The tiered decision logic -- Context for simple, Zustand for medium complexity, Jotai for atomic state -- matches the decision framework that practitioners use in 2025. Zustand at roughly 1KB gzipped carries almost no bundle cost, and Jotai's per-atom subscriptions mean components only re-render when the atoms they actually use change. The instruction to preserve TypeScript types is critical -- a refactor that silently drops readonly or widens a union type introduces runtime risk. The escape hatch at step 5 is equally important: it tells the AI that not everything is global state, preventing the common mistake of lifting perfectly local props into a shared store.
The Anti-Prompt
Bad version:
Fix my prop drilling.
Why it fails: The AI has no context about your component tree, no signal about how complex your state actually is, and no constraint on TypeScript. You will get a generic Context API example that may not fit your use case, and you will likely end up with a single monolithic context that causes every consumer to re-render on every update -- which is measurably worse performance than splitting by domain or moving to Zustand. Without the "leave genuine props alone" instruction, the AI may also extract props that should stay local, making your components harder to test in isolation.
Variations
Variation 1 -- Force Zustand across the board:
Add to the prompt: "Use Zustand for all refactored state. Create a single store file with typed slices. Use selector functions in every component to subscribe only to the state it needs."
Use this when you have a medium-to-large app with auth, cart, or feature flags that multiple unrelated subtrees need to access. Zustand's store lives outside the React tree, so you can read and update it from utility functions and WebSocket handlers without workarounds.
Variation 2 -- Force React Context with performance guards:
Add to the prompt: "Use React Context only. Split contexts by update frequency -- do not put fast-changing state in the same context as slow-changing state. Wrap context values in useMemo."
Use this when you want zero external dependencies and your state changes infrequently. Splitting contexts by domain reduces the re-render surface the same way component-level memoization does.
Variation 3 -- Jotai for atomic/form state:
Add to the prompt: "Use Jotai atoms. Each independent piece of state should be its own atom. Where one value is derived from another, use a derived atom with the atom(get => ...) pattern. Avoid putting unrelated state in a single atom."
Use this when you have a complex form, a real-time dashboard, or a collaborative UI where different sections update independently at high frequency. Jotai's atomic subscriptions mean a change to one atom does not trigger re-renders in components that use different atoms.
When You Would Actually Use This
You are three weeks into a feature and a new designer asks for a loading spinner in the page header that reflects whether any data fetch is in progress. You trace the isLoading flag up through four components to find it lives in a deeply nested data component. Rather than threading a new prop through the tree, this prompt gets you a Zustand action and a single selector in the header component within one AI pass -- and your intermediate components stay untouched.
Ask The Guild
Have you ever extracted something into global state that you later regretted? What was the prop that should have stayed a prop? Share your story in the thread.