mrkeyoor.com_
Thu 06 Aug 02:41 UTC
npmWeb Frontendupdated 06 Aug 2026

react-redux

react-redux is the glue between a Redux store and React components. It does not hold state itself: Redux owns the state, and this package handles the part React needs, which is subscribing to the store, working out whether the slice a component reads actually changed, and re-rendering only the components affected. You wrap the tree in a Provider that carries the store through context, then read state with useSelector and send actions with useDispatch. Under the hood it uses React's useSyncExternalStore, so it behaves correctly with concurrent rendering and server rendering instead of tearing. The older connect() higher-order component still works and is not going away, but as of 9.3.0 it is marked deprecated in the types, and the hooks are what the maintainers tell everyone to write today.

Verdict

If your app runs Redux, this is the binding, it is well maintained, and 3.8 KB gzipped is a fair price. If you are choosing a state library from scratch in 2026, decide whether you want Redux at all first, because that is the real cost, not this package.

API stability5/5The hooks API has been the same since v7 in 2019, and the v8 and v9 majors were about React version support and packaging rather than rewrites. connect() is deprecated in the types as of 9.3.0 but the maintainers state plainly that it keeps working and will not be removed.
Docs5/5react-redux.js.org has a full API reference, a TypeScript guide, and explicit pages on why a selector re-renders too often, and the release notes explain the reasoning behind each change at length.
Maintenance5/5Pushed August 2026 with 28 open issues on a repo of this size, maintained by the same team as Redux and Redux Toolkit, and React 19 support landed within days of that release.
Ecosystem5/5Around 33.4M weekly downloads with Redux Toolkit, RTK Query, Redux DevTools, reselect, and redux-persist all built around it, so almost any question you hit has been answered somewhere already.

Use it if

  • You already run Redux or Redux Toolkit and need React components to read from that store; this is the official binding and there is no real competitor for the job
  • Your app has genuinely global, frequently updated state read by many components at different depths, and you want the Redux DevTools time-travel and action log to debug it
  • You want fine-grained subscriptions: each useSelector re-renders only its own component when its slice changes, instead of re-rendering a whole context subtree
  • You are on TypeScript and want pre-typed hooks: useSelector.withTypes<RootState>() and useDispatch.withTypes<AppDispatch>() give every call site the right types with one line of setup
Skip it if

Setup reality

npm install react-redux redux (or @reduxjs/toolkit, which most people should use for the store half). The peer dependencies are strict: React 18 or 19, Redux 5, and @types/react 18 or 19 on TypeScript projects, so npm will refuse the install on an older stack rather than warn. Then you wrap the app in <Provider store={store}> exactly once, at a level above everything that reads state. Two setup details bite people. In Next.js App Router the Provider file needs 'use client' at the top and the store must be created per request inside a component rather than as a module singleton, otherwise state leaks between users on the server. And on TypeScript you should immediately export pre-typed hooks with .withTypes, because raw useSelector gives you unknown state.

Patterns

Wrap the app once with Providerprovider-setup

import { Provider } from 'react-redux';
import { createRoot } from 'react-dom/client';
import { store } from './store';
import App from './App';

createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
);

One Provider, above everything that reads state. Any component calling useSelector outside it throws at render time with a message about a missing store.

Read a value with useSelectorread-state-with-selector

import { useSelector } from 'react-redux';

function CartBadge() {
  const count = useSelector(state => state.cart.items.length);
  return <span>{count}</span>;
}

Select the narrowest primitive you can. The selector runs after every dispatched action and the result is compared with reference equality, so returning a number re-renders only when that number changes.

Send actions with useDispatchdispatch-actions

import { useDispatch } from 'react-redux';
import { itemAdded } from './cartSlice';

function AddButton({ product }) {
  const dispatch = useDispatch();
  return (
    <button onClick={() => dispatch(itemAdded(product))}>Add</button>
  );
}

The dispatch reference is stable for the lifetime of the store, so it is safe in a useEffect or useCallback dependency array without causing loops.

Pre-type the hooks once for TypeScripttyped-hooks

// hooks.ts
import { useDispatch, useSelector, useStore } from 'react-redux';
import type { AppDispatch, AppStore, RootState } from './store';

export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();

.withTypes arrived in 9.1.0 and replaces the old TypedUseSelectorHook cast. Import these everywhere instead of the raw hooks, or thunks and async dispatch will not type-check.

Stop a selector that returns a new object every timeavoid-rerender-new-object

import { useSelector, shallowEqual } from 'react-redux';

// re-renders on every action: new object identity each run
const bad = useSelector(state => ({ id: state.user.id, name: state.user.name }));

// compares one level deep instead
const user = useSelector(
  state => ({ id: state.user.id, name: state.user.name }),
  shallowEqual
);

This is the single most common react-redux bug. shallowEqual fixes objects and arrays of primitives; it does not help if the values inside are themselves newly created objects.

Derive filtered data with createSelectormemoized-derived-data

import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from './hooks';

const selectDoneTodos = createSelector(
  [state => state.todos.items],
  items => items.filter(t => t.done)
);

function DoneList() {
  const done = useAppSelector(selectDoneTodos);
  return <ul>{done.map(t => <li key={t.id}>{t.text}</li>)}</ul>;
}

Define the selector at module scope, not inside the component, or the memo cache is thrown away on every render. reselect ships inside Redux Toolkit, so there is nothing extra to install.

Pass a prop into a selectorselector-with-argument

import { useCallback } from 'react';
import { useAppSelector } from './hooks';

function TodoRow({ id }) {
  const todo = useAppSelector(
    useCallback(state => state.todos.byId[id], [id])
  );
  return <li>{todo.text}</li>;
}

An inline arrow closing over a prop is a new function each render, which is fine for cheap lookups but defeats any memoization. useCallback keeps the identity stable while id is unchanged.

Provider in the Next.js App Routernextjs-app-router-provider

'use client';
import { useRef } from 'react';
import { Provider } from 'react-redux';
import { makeStore, type AppStore } from './store';

export function StoreProvider({ children }) {
  const storeRef = useRef<AppStore>(undefined);
  if (!storeRef.current) storeRef.current = makeStore();
  return <Provider store={storeRef.current}>{children}</Provider>;
}

A module-level store singleton is shared across requests on the server and leaks one user's state into another's response. Creating it in a ref gives each request its own store. The 'use client' directive is required because hooks throw in the react-server build.

Read state without subscribingread-store-imperatively

import { useAppStore, useAppDispatch } from './hooks';

function SaveButton() {
  const store = useAppStore();
  const dispatch = useAppDispatch();

  const onClick = () => {
    const draft = store.getState().editor.draft; // read at click time
    dispatch(saveDraft(draft));
  };

  return <button onClick={onClick}>Save</button>;
}

useStore does not subscribe, so the component never re-renders from it. Use it in event handlers when you need the latest value; never call getState() during render, because that read is invisible to React.

A second store in its own contextcustom-context

import { createContext } from 'react';
import {
  Provider,
  createSelectorHook,
  createDispatchHook,
  createStoreHook,
} from 'react-redux';

export const WidgetContext = createContext(null);
export const useWidgetSelector = createSelectorHook(WidgetContext);
export const useWidgetDispatch = createDispatchHook(WidgetContext);
export const useWidgetStore = createStoreHook(WidgetContext);

// <Provider context={WidgetContext} store={widgetStore}>...</Provider>

Useful for an embeddable widget that must not collide with the host app's store. Regular useSelector still reads the default context, so the two trees stay independent.

Render a component under test with a storetest-with-provider

import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { makeStore } from './store';

function renderWithStore(ui, { preloadedState } = {}) {
  const store = makeStore(preloadedState);
  return {
    store,
    ...render(<Provider store={store}>{ui}</Provider>),
  };
}

Build a real store with preloaded state rather than mocking useSelector. A fresh store per test keeps cases isolated, and you can assert on store.getState() after interactions.

Replace a connect() component with hooksmigrate-connect-to-hooks

// before
export default connect(
  state => ({ user: state.auth.user }),
  { logout }
)(Header);

// after
function Header() {
  const user = useAppSelector(state => state.auth.user);
  const dispatch = useAppDispatch();
  return <button onClick={() => dispatch(logout())}>{user.name}</button>;
}

9.3.0 marks connect as deprecated in the types, which shows as a strikethrough in editors. Behaviour is unchanged and removal is not planned, so migrate at your own pace; legacy_connect is exported if the warning is noisy.

Alternatives

PackageRegistryPick it when
zustandnpmYou want shared state in a hook with no provider, no actions, and no reducer boilerplate.
jotainpmYour state is naturally lots of small independent pieces rather than one tree.
@tanstack/react-querynpmThe state you keep syncing is server data that needs caching, refetching, and invalidation.