Connect components to the store — typed useSelector and useDispatch hooks, reading just the slice you need, dispatching actions from event handlers, and selecting derived values.
Why: two hooks connect a component to the store — useSelector reads a value out of it, and useDispatch sends actions to it. You COULD use them directly, but you would have to repeat the store's types every time. So the standard practice is to wrap them once with your types and import those wrappers everywhere instead. Note: this is a five-line file you write a single time per project.
// app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Pre-typed versions of the two hooks — import these everywhere
// instead of the raw ones, and your store's types come along for free.
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()Why: useAppSelector takes a function that picks one value out of the whole store, and returns just that value. The component re-renders only when THAT value changes — not when other parts of the store change. Note: select the smallest piece you actually need. Reading state.counter.value re-renders less often than grabbing the whole state.counter object.
'use client'
import { useAppSelector } from '../hooks'
export default function CounterDisplay() {
// Pick exactly the value you need out of the store.
// This component re-renders only when counter.value changes.
const count = useAppSelector((state) => state.counter.value)
return <p>Current count: {count}</p>
}Why: to change the store, get the dispatch function from useAppDispatch and call it with an action — dispatch(increment()). Note the parentheses: increment() BUILDS the action object, and dispatch SENDS it. The matching reducer in your slice then runs and updates the store, and every component reading that value re-renders with it.
'use client'
import { useAppDispatch } from '../hooks'
import { increment, reset, incrementBy } from '../counterSlice'
export default function CounterButtons() {
const dispatch = useAppDispatch()
return (
<div>
{/* increment() builds the action; dispatch sends it to the store */}
<button onClick={() => dispatch(increment())}>+1</button>
{/* Actions that carry data: pass it to the action creator */}
<button onClick={() => dispatch(incrementBy(5))}>+5</button>
<button onClick={() => dispatch(reset())}>Reset</button>
</div>
)
}Why: a real component usually does both — reads a value to show it and dispatches actions to change it. Notice the two components from the last topics did not pass anything to each other: both talk to the store directly. That is the point of Redux — shared state lives in one place, so distant components stay in sync without prop-drilling.
'use client'
import { useAppSelector, useAppDispatch } from '../hooks'
import { increment, incrementBy } from '../counterSlice'
export default 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(incrementBy(10))}>+10</button>
</div>
)
}