Sign in
Topics
Start building React apps faster and with clarity
Learn how to decide between local component state and a global store in React. Understand their impact on rendering, bundle size, and maintainability, with practical tips for keeping apps fast, scalable, and easy to manage.
At some point in building a React application, you’ll ask: “Should this state live here, or in a global store?”
The choice impacts speed, maintainability, and complexity. Use local state wisely, and you’ll prevent unnecessary re-renders. Misjudge it, and you may face slow initial load times or a large JavaScript bundle that slows your app.
Choosing between a local state and a global store affects rendering cost, bundle size, and developer clarity. Developers should use only the necessary code and functional components to keep things lean. Misuse can lead to slow initial load times, unnecessary re-renders, or shipping an entire bundle upfront when parts could be loaded selectively based on user needs.
My focus is on clarity in React application structure, controlling the rendering process, and avoiding performance issues caused by unnecessary DOM nodes, unnecessary re-renders, or huge JavaScript bundles.
Local state lives inside a React component, usually via the useState
hook in functional components. For example:
1import react, { useState } from 'react' 2 3export default function Counter() { 4 const [count, setCount] = useState(0) 5 return ( 6 <> 7 <button onClick={() => setCount(count + 1)}> 8 Clicked {count} times 9 </button> 10 </> 11 ) 12}
Here you import react
, use functional components, define a count state
with const \[count, setCount\]
. This example uses react fragments
(<>…</>
) to group multiple elements.
It helps manage a particular page or child components without sending updates to unrelated areas.
prevent unnecessary re-renders — changes stay local.
Keeps the application performance smooth, as only the initial render and minimal updates occur.
Within React, local refers to component-level state; global variables often imply top-level JavaScript outside components, which is terrible for reactivity. But when deciding local vs global state management, here’s what matters.
Use local state when:
The data affects just one or a few child components
You want simple updates, like form inputs or toggle switches
You want only the necessary code in a single file, no overhead of global state
Use global state when:
Multiple components—possibly far apart—need the same data
You want central control, change history, or a persistent state
You want a predictable flow across many functional components
“I’ve learned that a variable inside a function lives in a bubble. If I want to use a global variable, I need to be careful not to accidentally shadow it.” — LinkedIn Post
A global store may use Redux , Recoil , Zustand , or Context . It holds the global state and lets many components access or change data via providers or hooks. It’s a popular library pattern in React apps.
For a small React application, a Context may be enough. But if your app grows or data flows widely, a robust store helps.
When should you use a context to manage state instead of Redux local state? Use Context when your needs are small—like theme or auth—so you avoid shipping a large JavaScript bundle and causing slow initial load times. Use Redux (or similar) when you need middleware, time-travel debugging, or strict organization.
You almost always mix both. Use local state for ephemeral UI like toggles, inputs, and counters. Use global state for JSON data shared across child components, login, or theme.
In one React component, you might:
1import react, { useState, useContext } from 'react' 2import { UserContext } from './UserContext' 3 4export default function Profile() { 5 const user = useContext(UserContext) 6 const [editMode, setEditMode] = useState(false) 7 8 return ( 9 <> 10 {editMode ? ( 11 <EditForm user={user} /> 12 ) : ( 13 <ViewProfile user={user} /> 14 )} 15 <button onClick={() => setEditMode(!editMode)}> 16 Toggle Edit 17 </button> 18 </> 19 ) 20}
Here, local state controls toggling, while global state via context holds user data. This combination helps manage performance, avoiding unnecessary re-renders.
A major part of optimizing performance is stopping unnecessary re-renders. Use strategies like:
Memoization: useMemo
, useCallback
, avoiding re-creating functions
Avoid passing inline arrow functions to child components
Limit global state changes so only components depending on data re-render
Use React DevTools
to spot wasted renders.
When dealing with large datasets in a React application, like long lists, use list virtualization (e.g., React Window or React Virtualized). That keeps only visible rows in the DOM, reducing DOM nodes and improving scroll performance when the user scrolls.
Want to take your React state management game to next level? Launch your next project with Rocket.new and go from idea to production in record time.
Code splitting breaks your app into multiple chunks. You can lazy load images and components so they are loaded selectively based on route or scroll.
1import react, { Suspense, lazy } from 'react' 2 3const HeavyComponent = lazy(() => import('./HeavyComponent')) 4 5export default function App() { 6 return ( 7 <> 8 <Suspense fallback={<div>Loading...</div>}> 9 <HeavyComponent /> 10 </Suspense> 11 </> 12 ) 13}
You also import React and React Fragments for grouping. Notice lazy loading defers huge chunks, avoiding an entire bundle upfront. To prevent unnecessary re-renders, ensure your child components are wrapped in React.memo
when suitable.
As your React app grows, you’ll face performance bottlenecks. Key practices:
Modularize into manageable chunks with code splitting
Use export default per component, keeping each in a single file
Avoid shipping a large JavaScript bundle or entire bundle upfront
Use lazy-loading images to defer heavy media
Use useMemo and useCallback
Avoid unreferred unnecessary DOM nodes
Monitor initial render times
Test in production mode to simulate real behavior
This helps keep your application performance top-notch.
For many rows or tabular data, use list virtualization. For example, use FixedSizeList
from React Window. This avoids rendering all items and reduces the number of DOM nodes. When the user scrolls, only visible items render, improving speed and lowering memory.
You can combine virtualization with lazy-loading images, so images only load when visible. That saves bandwidth and CPU.
In functional components, use useEffect
for mounting, updating, and teardown. Use it to fetch data selectively:
1import react, { useEffect } from 'react' 2 3export default function DataLoader({ id }) { 4 useEffect(() => { 5 fetchData(id) 6 }, [id]) 7 8 return <div>Data for {id}</div> 9}
This avoids loading data on every render. You should avoid redundant calls (which cause unnecessary re-renders) and thus improve rendering process performance.
1import react, { useState, useContext, lazy, Suspense } from 'react' 2import { GlobalStore } from './GlobalStore' 3 4const Chart = lazy(() => import('./Chart')) 5 6export default function Dashboard() { 7 const global = useContext(GlobalStore) 8 const [filter, setFilter] = useState('all') 9 10 const filteredData = global.data.filter(item => item.type === filter) 11 12 return ( 13 <> 14 <Select value={filter} onChange={e => setFilter(e.target.value)} /> 15 <Suspense fallback={<div>Loading chart...</div>}> 16 <Chart data={filteredData} /> 17 </Suspense> 18 </> 19 ) 20}
Here, the local state handles the filter, and the global state provides data. The chart is lazy-loaded, so we avoid the entire bundle upfront and cut the initial payload. This balances simplicity, optimizing performance, and clarity.
In closing, this post guides when to use local state versus a global store, how to build your React application in functional components, apply code splitting, lazy loading, and avoid performance pitfalls like slow initial load times or unnecessary re-renders.
By writing only the necessary code, grouping elements with React fragments, optimizing heavy parts, and thoughtfully choosing between local and global, you can keep your React app responsive, maintainable, and scalable.
The way forward is clear: use local state when it suffices, resort to a global store when coordination demands it. This insight on when to use local component state vs a global store marks the heart of building high-performance React apps.