useSelector and useDispatch

useSelector(fn) subscribes a component to the store and returns fn(state); useDispatch() returns the store's dispatch. Between them they replace connect, with no wrapper component.

Two components, two subscriptions, two independent re-render countsHTML
<style>body{font:15px/1.5 system-ui;margin:10px}
.b{border:1px solid #90a4ae;border-radius:6px;padding:6px 10px;margin:6px 0}</style>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<script type="importmap">{"imports":{"react":"https://esm.sh/react@19.3.0",
"react-dom/client":"https://esm.sh/react-dom@19.3.0/client?external=react",
"@reduxjs/toolkit":"https://esm.sh/@reduxjs/toolkit@2.12.0?external=react",
"react-redux":"https://esm.sh/react-redux@9.3.0?external=react,react-dom"}}</script>
<script type="text/babel" data-type="module">
import React, { useRef } from 'react';
import { createRoot } from 'react-dom/client';
import { configureStore } from '@reduxjs/toolkit';
import { Provider, useSelector, useDispatch } from 'react-redux';
const cart = (s = { count: 0 }, a) => a.type === 'cart/added' ? { count: s.count + 1 } : s;
const user = (s = { name: 'guest' }, a) => a.type === 'user/named' ? { name: a.payload } : s;
const store = configureStore({ reducer: { cart, user } });
function CartPanel() {
  const count = useSelector(s => s.cart.count);      // subscribes to cart only
  const dispatch = useDispatch();
  const n = useRef(0); n.current += 1;
  return <div className="b">items: <b>{count}</b> — rendered {n.current}x{' '}
    <button onClick={() => dispatch({ type: 'cart/added' })}>Add item</button></div>;
}
function UserPanel() {
  const name = useSelector(s => s.user.name);        // subscribes to user only
  const n = useRef(0); n.current += 1;
  return <div className="b">user: <b>{name}</b> — rendered {n.current}x</div>;
}
createRoot(document.getElementById('root')).render(
  <Provider store={store}><CartPanel /><UserPanel /></Provider>);
[100, 200, 300].forEach(t => setTimeout(() => document.querySelector('button').click(), t));
</script>
Browser output of Listing 2.168
Browser output of 168

Three dispatches re-rendered the cart four times and the user panel once: every selector runs after every action, but a component re-renders only when its selector's result changes by reference equality.

That makes the one real pitfall obvious. useSelector(s => ({ a: s.a, b: s.b })) builds a new object every call, so it never equals the previous result and the component re-renders on every action in the application. Three fixes, best first: one useSelector call per value, as above; shallowEqual from react-redux as the second argument; or a memoized selector (createSelector).

dispatch keeps a stable identity for the life of the store, so it is safe in dependency arrays. An inline arrow selector is fine, but a memoized selector must not be created inline: a new instance starts with an empty cache every render.