Choosing the right location for state prevents complexity.
The Principle: Lift Only When Needed
jsx// ❌ Don't lift unnecessarily // Parent doesn't need to know about hover state function Parent() { const [isButtonHovered, setIsButtonHovered] = useState(false); return <Button isHovered={isButtonHovered} onHover={setIsButtonHovered} />; } // ✅ Keep state local when possible function Button() { const [isHovered, setIsHovered] = useState(false); return ( <button onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > Click me </button> ); }
When to Lift State
1. Siblings Need to Share
jsxfunction ProductPage() { const [selectedSize, setSelectedSize] = useState('M'); return ( <> {/* Both need selectedSize */} <SizeSelector selected={selectedSize} onChange={setSelectedSize} /> <AddToCartButton size={selectedSize} /> </> ); }
2. Parent Needs to React
jsxfunction Form() { const [isValid, setIsValid] = useState(false); return ( <> <EmailInput onValidChange={setIsValid} /> <SubmitButton disabled={!isValid} /> </> ); }
Colocation Pattern
Keep state close to where it's used:
jsx// ❌ State too far from usage function App() { const [searchQuery, setSearchQuery] = useState(''); return ( <Layout> <Header> <SearchBar value={searchQuery} onChange={setSearchQuery} /> </Header> <Main> <SearchResults query={searchQuery} /> </Main> </Layout> ); } // ✅ State colocated with components that need it function SearchSection() { const [searchQuery, setSearchQuery] = useState(''); return ( <> <SearchBar value={searchQuery} onChange={setSearchQuery} /> <SearchResults query={searchQuery} /> </> ); }
Derived State vs Stored State
jsx// ❌ Redundant state function ProductList({ products }) { const [filteredProducts, setFilteredProducts] = useState(products); const [filter, setFilter] = useState(''); useEffect(() => { setFilteredProducts( products.filter(p => p.name.includes(filter)) ); }, [products, filter]); return /* ... */; } // ✅ Derive instead of store function ProductList({ products }) { const [filter, setFilter] = useState(''); // Computed during render const filteredProducts = products.filter( p => p.name.includes(filter) ); return /* ... */; }
State Location Checklist
- Can it be derived? → Don't store, compute
- Only one component needs it? → Local state
- Parent and children need it? → Lift to parent
- Distant components need it? → Context or store
- From server? → Server state library
- In URL? → Router state