Lifting state higher than necessary causes unnecessary re-renders in the parent and all siblings. Keep state as close as possible to where it is consumed.
reduces unnecessary re-renders and simplifies component responsibilities
BeforeMerge scans your pull requests against this rule and 5+ others. Get actionable feedback before code ships.
Impact: MEDIUM (reduces unnecessary re-renders and simplifies component responsibilities)
A common anti-pattern is lifting all state to the nearest common ancestor "just in case" or out of habit. When state lives higher in the tree than it needs to, every update to that state re-renders the parent and all of its children — even siblings that have nothing to do with that state. This also clutters the parent component with state management logic it shouldn't own. State should be colocated with the component (or subtree) that actually reads and writes it.
Incorrect (state lifted too high — search input state in a page-level component):
// ❌ SearchPage owns the search query state, causing the entire page
// (header, sidebar, footer) to re-render on every keystroke
function SearchPage() {
const [query, setQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState('all')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
const results = useSearchResults(query, selectedCategory, sortOrder)
return (
<div>
<Header /> {/* ❌ Re-renders on every keystroke */}
<Sidebar /> {/* ❌ Re-renders on every keystroke */}
<SearchBar value={query} onChange={setQuery} />
<FilterBar
category={selectedCategory}
onCategoryChange={setSelectedCategory}
sortOrder={sortOrder}
onSortChange={setSortOrder}
/>
<ResultsList results={results} />
<Footer /> {/* ❌ Re-renders on every keystroke */}
</div>
)
}Correct (colocate state with the subtree that uses it):
// ✅ SearchPage is a thin layout shell — no unnecessary state
function SearchPage() {
return (
<div>
<Header />
<Sidebar />
<SearchSection /> {/* All search state lives here */}
<Footer />
</div>
)
}
// ✅ State is colocated — only this subtree re-renders on changes
function SearchSection() {
const [query, setQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState('all')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
const results = useSearchResults(query, selectedCategory, sortOrder)
return (
<section>
<SearchBar value={query} onChange={setQuery} />
<FilterBar
category={selectedCategory}
onCategoryChange={setSelectedCategory}
sortOrder={sortOrder}
onSortChange={setSortOrder}
/>
<ResultsList results={results} />
</section>
)
}Additional context:
Detection hints:
# Find components with many useState calls — potential over-lifting
grep -rn "useState" src/ --include="*.tsx" --include="*.ts"Reference: React docs on choosing the state structure