In React-Redux 9, what does connect() do for render performance that useSelector does not, and why do most codebases still prefer hooks?
answer
- a memoised wrapper component
- shallow comparison of mapped props
- parent renders pass straight through hooks
- less wiring, simpler types
basics
~20 sconnect wraps the component in React.memo and shallow-compares the mapState result and incoming props, so it skips parent-driven renders. useSelector compares only its own result and never blocks parent renders, yet hooks need far less code and typing.
solid answer
~50 s`connect(mapStateToProps, mapDispatchToProps)(Component)` produces a wrapper that is always memoised — React-Redux wraps it in `React.memo`, and the old `pure` option has been removed. By default it skips `mapStateToProps` when the root state is `===` unchanged, shallow-compares the object `mapStateToProps` returns, and shallow-compares incoming own props, so the wrapped component renders only when its merged props actually change, including when its parent re-renders with the same props. `useSelector` compares only its own result, by `===` unless you pass an equality function, and does nothing about parent-driven renders; you add `React.memo` yourself. Hooks still win for most code: no higher-order component wrapping, selectors and dispatch written where they are used, easy composition into custom hooks, and far simpler TypeScript than `ConnectedProps`. In React-Redux 9.3 `connect` still works but is marked `@deprecated` to steer new code to hooks; `legacy_connect` is the same function without the editor warning.
code
tsx · 18 linesimport { memo } from 'react'
import { connect, useSelector } from 'react-redux'
import type { RootState } from './store'
type Props = { label: string; count: number }
function CountView({ label, count }: Props) {
return <span>{label}: {count}</span>
}
// connect: memoised wrapper, mapState result shallow-compared
const mapState = (state: RootState) => ({ count: state.todos.length })
export const ConnectedCount = connect(mapState)(CountView)
// hooks: add memo yourself if parent renders are frequent
export const HookCount = memo(function HookCount({ label }: { label: string }) {
const count = useSelector((state: RootState) => state.todos.length)
return <CountView label={label} count={count} />
})go deeper
Recall that connect wraps a component and injects props from mapStateToProps and mapDispatchToProps, while hooks read and dispatch inside the component.
Explain connect's default comparisons and memo wrapper versus useSelector's single === comparison, and what that means for parent-driven renders.
Decide when to add React.memo and stable selectors to hook-based components, and when leaving working connect code in place is the better call.
Plan whether and how a codebase migrates from connect to hooks, weighing churn and review cost against typing and readability gains.
## Two APIs over the same store React-Redux offers two ways for a component to read state and dispatch actions: - **`connect()`**, a higher-order component: `connect(mapStateToProps, mapDispatchToProps)(TodoCount)` returns a new component that computes props from the store and passes them to `TodoCount`. - **Hooks**: `useSelector` and `useDispatch`, called inside the component itself. Both subscribe to the same store. They differ in **how much they compare** before rendering, and in how the code reads. ## What connect compares by default | Step | Default check | Effect | |---|---|---| | Store state changed? | `areStatesEqual`: `===` on the root state | skip `mapStateToProps` entirely if the state reference is unchanged | | Mapped state changed? | `areStatePropsEqual`: `shallowEqual` on the object `mapStateToProps` returns | a new wrapper object with the same field values counts as unchanged | | Own props changed? | `areOwnPropsEqual`: `shallowEqual` on props from the parent | a parent re-render with the same props does not re-render the wrapped component | | Final props changed? | `areMergedPropsEqual`: `shallowEqual` | render only if the merged props differ | On top of that, the connected wrapper is wrapped in **`React.memo`**. The old `pure` option that turned this off has been **removed**; passing it only logs a warning, because connect is now always memoised. Two consequences: 1. `mapStateToProps` may return a new object literal every time, `{ count: state.count }`, without causing re-renders, because the result is compared field by field. 2. A connected component is **insulated from parent re-renders** as long as its own props are shallowly equal. `mapStateToProps` declared with two parameters, `(state, ownProps)`, is also re-run when own props change; declared with one, it is called only when the store state changes. ## What useSelector compares `useSelector` is narrower: - it compares **its own selector's result**, with `===` by default; - it does **not** memoise the component, so a parent re-render re-renders it regardless of props; - an object literal returned from a selector re-renders on every dispatch unless you pass `shallowEqual`. To get connect-like insulation with hooks, you combine them: return stable values from each `useSelector` call and wrap the component in `React.memo` where parent renders are frequent. ## Why hooks are still the default - **Less code**: no separate `mapState` and `mapDispatch` functions, no wrapper component in the tree. - **Colocation**: the component reads exactly what it renders, next to where it renders it. - **Composition**: selectors and dispatch logic go into custom hooks, such as `useCurrentUser()`. - **TypeScript**: `useSelector.withTypes<RootState>()` infers everything, whereas `connect` needs `ConnectedProps<typeof connector>` to type the injected props. - **Tooling**: the React DevTools tree has fewer wrapper layers. ## Where connect still has a case - Existing codebases full of connected components: `connect` still works in React-Redux 9.3, and rewriting working code gives little. It is now marked `@deprecated` in its type declarations as a nudge toward hooks, and `legacy_connect` is exported as the identical function without that editor strike-through; nothing was removed. - Strict container/presentational separation, where a presentational component should stay unaware of Redux and receive plain props; though a small wrapper component using hooks achieves the same. - Subscription ordering: `connect` establishes nested subscriptions from parent to child, and React-Redux's docs note that hooks can reintroduce the **stale props / zombie child** edge cases that this ordering avoids, so hook-based children should tolerate a missing entity. ## A migration sketch Moving a connected component to hooks is mechanical when done one component at a time: 1. Turn the body of `mapStateToProps` into named selectors. 2. Call `useSelector` once per value inside the component, or group values with `shallowEqual` where they belong together. 3. Replace `mapDispatchToProps` with `useDispatch` and plain handlers, or a custom hook. 4. Add `React.memo` where the parent re-renders often, since `connect` used to absorb those renders. 5. Delete the `ConnectedProps` types and the wrapper export. ## Summary - `connect`: memoised wrapper, shallow comparison of mapped and own props, skips parent-driven renders; - `useSelector`: `===` on one value, no protection from parent renders; - hooks are preferred for simplicity and typing; add `React.memo` and stable selectors where profiling shows a need.
- Why can a React-Redux mapStateToProps return a new object literal every time without causing extra renders?`connect` compares the object `mapStateToProps` returns with `shallowEqual` by default, field by field. If every field is `===` to its previous value, the wrapped component does not re-render, even though the wrapper object itself is new.
- When does React-Redux call a mapStateToProps declared as (state, ownProps)?Whenever the store state changes and whenever the connected component receives new own props by shallow comparison. Declared with only `state`, it is called only when the store state changes, which is cheaper when the mapping does not depend on props.
saying these in an interview costs you the question
- useSelector memoises the component like connect does
- connect was removed from React-Redux 9 and no longer works
- Setting pure: false in connect disables memoisation
- mapStateToProps must return a memoised object to avoid renders
- Hooks and connect use different stores