Redux Toolkit 02 ⚛️ Provider Setup — Connecting store with React Redux
The previous chapter created the store with configureStore and exported the RootState and AppDispatch types. A store, however, is inert until React components can reach it. The Provider component from react-redux is the bridge. It accepts the store as a prop and distributes it through React’s context mechanism, so that every descendant component can read state with useSelector and dispatch actions with useDispatch without receiving the store as a prop at every level .
This chapter covers the placement of the Provider at the application root, the error that occurs when it is missing, the typed hooks pattern that connects components to the store’s inferred types, and the SSR-specific pattern for creating a store per request. The provider is the single point of connection between Redux and React; every component subscription depends on it being correct.
Key point: The Provider must wrap the component tree at or above the level where any useSelector or useDispatch call occurs. It accepts exactly one store prop. The store is created once at module scope for client-side applications, but in SSR-heavy frameworks it must be created inside a client component with useState to avoid sharing a store instance across requests .
Why the Provider is required
The context distribution problem.
React Redux does not use a global variable to expose the store. It uses React context. The Provider component renders a context provider that places the store reference into the React tree. The useSelector hook calls useContext on that context to retrieve the store, and the useDispatch hook does the same . Without the provider, the context value is null, and the hooks throw an invariant violation: “Could not find ‘store’ in the context” . The provider is not an optional wrapper; it is the mechanism that makes the store visible to React.
The single-store problem.
Redux was designed around a single store. The Provider accepts one store, and the hooks read from that store. This is not a limitation to work around; it is the architecture that makes the state tree predictable. If multiple stores were needed—an uncommon and usually avoidable scenario—the provider supports a custom context prop so that different parts of the tree can read from different stores . For standard applications, one store and one provider at the root are correct.
The SSR isolation problem.
In a client-side rendered application, creating the store at module scope is correct because the module is evaluated once in the browser. In a server-rendered application, the module is evaluated once on the server for every request that imports it. A module-scope store would be shared across all requests, leaking one user’s state into another’s response . The SSR pattern creates a store factory (makeStore) and calls it inside a client component with useState(makeStore), ensuring that each request receives a fresh store instance that is stable across re-renders within that request.
a. Placing the Provider at the application root
The Provider is placed in the entry file, wrapping the root component. In a Vite or Create React App application, this is main.tsx or index.tsx.
// src/main.tsx
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'
const root = createRoot(document.getElementById('root')!)
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
The store is imported from the file created in the previous chapter. It is created once when the module is evaluated, and the same instance is passed to the provider on every render. The React.StrictMode wrapper does not affect the provider; it only enables additional development checks for the component tree.
The provider can be placed inside a layout component if the application uses one, but it must be above any component that calls a Redux hook. Placing it too deep means that components above it cannot access the store.
b. Typed hooks and component consumption
With the provider in place, components use the hooks to read state and dispatch actions. The plain useSelector and useDispatch hooks work, but they do not know the shape of the store’s state or the types of the thunks that can be dispatched. The typed hooks from the previous chapter solve this.
// src/app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
A component then imports these hooks instead of the plain ones. The useAppSelector hook infers the state type automatically, so the selector does not need a type annotation. The useAppDispatch hook knows about thunks, so dispatching an async action does not produce a type error.
// src/features/counter/Counter.tsx
import { useAppSelector, useAppDispatch } from '../../app/hooks'
import { increment, decrement } from './counterSlice'
function Counter() {
// state is typed as RootState automatically
const count = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
)
}
The useAppSelector call subscribes the component to the counter.value slice. When that value changes, the component re-renders. When other parts of the state change, it does not. The useAppDispatch call returns a stable dispatch function that accepts both plain actions and thunks.
c. The SSR store provider pattern
In frameworks like Next.js with the App Router, the store cannot be a module-level singleton. The recommended pattern is to define a store factory and wrap it in a client component.
// src/lib/store.ts
import { configureStore } from '@reduxjs/toolkit'
import { counterReducer } from '../features/counter/counterSlice'
export const makeStore = () =>
configureStore({
reducer: {
counter: counterReducer,
},
})
export type AppStore = ReturnType<typeof makeStore>
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
The makeStore function returns a new store instance each time it is called. It is not called at module scope. The provider component uses useState to create the store once per request and keep it stable across renders.
// src/app/StoreProvider.tsx
'use client'
import { useState, type ReactNode } from 'react'
import { Provider } from 'react-redux'
import { makeStore, type AppStore } from '../lib/store'
export function StoreProvider({ children }: { children: ReactNode }) {
const [store] = useState<AppStore>(makeStore)
return <Provider store={store}>{children}</Provider>
}
The 'use client' directive is required because the provider uses React context, which only works in client components in the Next.js App Router . The useState(makeStore) call creates the store on the first render and returns the same instance on subsequent renders. A makeStore() call directly in the component body would create a new store on every render, losing all client state .
The StoreProvider is then used in the root layout, wrapping the application.
// src/app/layout.tsx
import { StoreProvider } from './StoreProvider'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<StoreProvider>{children}</StoreProvider>
</body>
</html>
)
}
Complete Example Session
// ============================================
// PART 1: THE STORE (CLIENT-SIDE)
// ============================================
// src/app/store.ts
// The store is created once at module scope
// This is correct for client-side applications
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// ============================================
// PART 2: TYPED HOOKS
// ============================================
// src/app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
// ============================================
// PART 3: PROVIDER IN THE ENTRY FILE
// ============================================
// src/main.tsx
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'
const root = createRoot(document.getElementById('root')!)
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
// ============================================
// PART 4: COMPONENT CONSUMING THE STORE
// ============================================
// src/features/counter/Counter.tsx
import { useAppSelector, useAppDispatch } from '../../app/hooks'
import { increment, decrement, incrementByAmount } from './counterSlice'
function Counter() {
const count = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
)
}
// ============================================
// PART 5: VERIFYING THE CONNECTION
// ============================================
// With the provider in place:
// - useAppSelector reads state.counter.value
// - useAppDispatch dispatches actions
// - No "Could not find store" error occurs
// - DevTools shows dispatched actions and state changes
// ============================================
// PART 6: SSR PATTERN (NEXT.JS APP ROUTER)
// ============================================
// src/lib/store.ts (factory, not singleton)
// export const makeStore = () => configureStore({ reducer: { counter: counterReducer } })
// export type AppStore = ReturnType<typeof makeStore>
// export type RootState = ReturnType<AppStore['getState']>
// export type AppDispatch = AppStore['dispatch']
// src/app/StoreProvider.tsx
// 'use client'
// import { useState } from 'react'
// import { Provider } from 'react-redux'
// import { makeStore } from '../lib/store'
//
// export function StoreProvider({ children }) {
// const [store] = useState(makeStore)
// return <Provider store={store}>{children}</Provider>
// }
// ============================================
// PART 7: ROOT LAYOUT WITH STORE PROVIDER
// ============================================
// src/app/layout.tsx
// import { StoreProvider } from './StoreProvider'
//
// export default function RootLayout({ children }) {
// return (
// <html><body><StoreProvider>{children}</StoreProvider></body></html>
// )
// }
The seven parts demonstrate the complete provider setup: the store, the typed hooks, the provider placement, the consuming component, and the SSR variant. The critical difference between Part 3 and Part 6 is the store lifetime: module-scope for client-side, per-request for server-side.
Quick Reference
Provider Placement
| Application Type | File | Pattern |
|---|---|---|
| Vite / CRA | main.tsx | <Provider store={store}><App /></Provider> |
| Next.js App Router | layout.tsx | <StoreProvider><App /></StoreProvider> |
| React Native | App.tsx | <Provider store={store}><App /></Provider> |
Hook Types
| Hook | Import | Purpose |
|---|---|---|
useSelector | react-redux | Read state (plain) |
useDispatch | react-redux | Dispatch actions (plain) |
useAppSelector | app/hooks | Read state (typed) |
useAppDispatch | app/hooks | Dispatch thunks (typed) |
SSR vs CSR Store Lifetime
| Environment | Store Creation | Provider Location |
|---|---|---|
| Client-side | Module scope | Entry file |
| SSR (Next.js) | useState(makeStore) | Client component |
Best Practices
✅ Do This:
// Wrap the root with Provider
<Provider store={store}><App /></Provider> // ✅
// Use typed hooks from hooks.ts
const count = useAppSelector((state) => state.counter.value) // ✅
// Create the store once at module scope for CSR
export const store = configureStore({...}) // ✅
// Use useState(makeStore) for SSR
const [store] = useState(makeStore) // ✅
// Add 'use client' to the SSR provider component
'use client' // ✅
❌ Don’t Do This:
// Render components without the Provider
root.render(<App />) // Hooks throw // ❌
// Create the store inside a component (CSR)
function App() { const store = configureStore({...}) } // ❌
// Use makeStore() directly in the SSR component body
const store = makeStore() // New store every render // ❌
// Import the module-scope store in an SSR component
import { store } from './store' // Shared across requests // ❌
// Use plain useDispatch for thunks
const dispatch = useDispatch() // Type error on thunks // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| “Could not find store” error | Provider missing from tree | Wrap root component |
| Thunk type error | Using plain useDispatch | Use useAppDispatch |
| State resets on every render (SSR) | makeStore() called in component body | useState(makeStore) |
| State leaks between requests (SSR) | Module-scope store imported | Use per-request factory |
| Selector not re-rendering | Selecting entire state or stale reference | Select specific slice |
| DevTools not connecting | Extension not installed | Install Redux DevTools |
Real-World Examples
1. Provider in entry file
<Provider store={store}><App /></Provider>
2. Typed selector
const value = useAppSelector((state) => state.counter.value)
3. Typed dispatch
const dispatch = useAppDispatch()
dispatch(increment())
4. Provider with StrictMode
<React.StrictMode><Provider store={store}><App /></Provider></React.StrictMode>
5. SSR store factory
export const makeStore = () => configureStore({ reducer: { counter: counterReducer } })
6. SSR provider component
'use client'
const [store] = useState(makeStore)
return <Provider store={store}>{children}</Provider>
7. Root layout with StoreProvider
<StoreProvider>{children}</StoreProvider>
8. Checking provider context
// The error "Could not find store" confirms the provider is missing
9. Multiple selectors in one component
const count = useAppSelector((s) => s.counter.value)
const name = useAppSelector((s) => s.user.name)
10. Dispatching from an event handler
<button onClick={() => dispatch(increment())}>+</button>
Visual
Provider as the Context Bridge
┌──────────────────────────────────────────────────────────────┐
│ PROVIDER DISTRIBUTES THE STORE THROUGH REACT CONTEXT │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Store (module scope) │ │
│ │ { counter: { value: 0 } } │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ │ │
│ passed as prop │
│ │ │
│ ┌──────────────────────────▼─────────────────────────────┐ │
│ │ <Provider store={store}> │ │
│ │ │ │
│ │ React Context.Provider value={store} │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ <App /> │ │ │
│ │ │ │ │ │
│ │ │ useAppSelector((s) => s.counter.value) │ │ │
│ │ │ │ │ │ │
│ │ │ └── reads store from context │ │ │
│ │ │ │ │ │
│ │ │ useAppDispatch() │ │ │
│ │ │ │ │ │ │
│ │ │ └── reads dispatch from context │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Without Provider: context value is null → hooks throw. │
└──────────────────────────────────────────────────────────────┘
CSR vs SSR Store Lifetime
┌──────────────────────────────────────────────────────────────┐
│ CLIENT-SIDE vs SERVER-SIDE STORE CREATION │
│ │
│ CSR (Vite, CRA): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ store.ts: export const store = configureStore({...}) │ │
│ │ main.tsx: <Provider store={store}> │ │
│ │ │ │
│ │ Module evaluated once in browser. │ │
│ │ One store for the lifetime of the page. │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ SSR (Next.js App Router): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ store.ts: export const makeStore = () => configureStore│ │
│ │ StoreProvider.tsx: │ │
│ │ 'use client' │ │
│ │ const [store] = useState(makeStore) │ │
│ │ │ │
│ │ Module evaluated on server for each request. │ │
│ │ useState creates one store per request. │ │
│ │ No cross-request state leakage. │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ⚠ Direct makeStore() in component body = new store/render │
└──────────────────────────────────────────────────────────────┘
Typed Hooks Flow
┌──────────────────────────────────────────────────────────────┐
│ TYPED HOOKS CONNECT COMPONENTS TO INFERRED TYPES │
│ │
│ store.ts: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ export type RootState = ReturnType<typeof store.getState>│ │
│ │ export type AppDispatch = typeof store.dispatch │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ hooks.ts: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ useAppSelector = useSelector.withTypes<RootState>() │ │
│ │ useAppDispatch = useDispatch.withTypes<AppDispatch>() │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ Component: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ const count = useAppSelector((s) => s.counter.value) │ │
│ │ // s is typed as RootState automatically │ │
│ │ │ │
│ │ const dispatch = useAppDispatch() │ │
│ │ dispatch(asyncThunk()) // No type error │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Provider import | import { Provider } from 'react-redux' |
| Provider prop | store (the configured store) |
| Placement | Root of component tree |
| Error without provider | “Could not find store in context” |
| Typed selector | useAppSelector from app/hooks |
| Typed dispatch | useAppDispatch from app/hooks |
| CSR store lifetime | Module scope |
| SSR store lifetime | useState(makeStore) in client component |
Key takeaways:
- The Provider is the only connection between Redux and React. Without it, the hooks throw. With it, every descendant component can read state and dispatch actions through context .
- The store is passed as a prop, not imported by components. Components use
useAppSelectoranduseAppDispatch, which read from the context that the provider established. Direct store imports bypass the subscription mechanism . - Typed hooks eliminate type annotations.
useAppSelectorinfersRootState, so selectors do not need(state: RootState)annotations.useAppDispatchknows about thunks, so async actions dispatch without type errors. - SSR requires a per-request store. A module-scope store is shared across requests on the server, leaking state between users. The
useState(makeStore)pattern creates one store per request and keeps it stable across renders . 'use client'is required for the SSR provider. React context only works in client components in the Next.js App Router. The provider component must be marked as a client component .- The provider does not need to wrap every component. It must wrap the root of the tree that contains any component using Redux hooks. Placing it at the entry file or root layout is the standard pattern.
Remember: The Provider is the bridge between the store and the component tree. It uses React context to distribute the store reference, and the hooks read from that context. The store is created once for client-side applications and once per request for server-side applications. The typed hooks connect components to the store’s inferred types. Without the provider, the hooks throw. Without the typed hooks, the components lose type safety. With both in place, the store is accessible from any component below the provider, and every subscription reads the correct slice of state.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!