useSelector: Reading Redux State in React Components
useSelector is the hook that reads from the Redux store and triggers a re-render when that slice of state changes. Learn why it exists and how to use it to render the task list.
The store is created and Provider is in place. The Redux state is initialized. What does not exist yet: any connection between that state and the task cards in the UI.
Two ways to read state exist. One is wrong for React. The other is useSelector.
Why Not store.getState() Directly?
You can import the store file and call store.getState() anywhere:
import store from '../../store';
const tasks = store.getState().tasks;This works once. The problem: the component does not re-render when state changes. React does not know the store exists. When a new task is dispatched, tasks still holds the old value. The UI freezes.
useSelector solves this. It subscribes the component to the Redux store. Every time the reducer returns a new state, any component that read that slice automatically re-renders with the fresh value.
Basic Usage
import { useSelector } from 'react-redux';
const tasks = useSelector((state) => state.tasks);The argument is a selector function. Redux calls it with the current state and the return value becomes the hook's return value. The property name tasks matches the key we gave in combineReducers, not the reducer function name.
Any name for the parameter works. Some teams use store instead of state. It does not matter, it represents the full Redux state object.
Rendering the Task List Dynamically
In Tasks.js, replace the hardcoded cards with a dynamic map:
import { useSelector } from 'react-redux';
export default function Tasks() {
const tasks = useSelector((state) => state.tasks);
// ... toggle state and handlers ...
return (
<div className="outer-container">
<div className="container">
{/* header, Collapsible form ... */}
<div className="content-body">
{tasks.map((task) => (
<div key={task.id} className="task">
<div className="task-body">
<div className="task-title">
<i className="fas fa-tasks" />
<span>{task.taskTitle}</span>
</div>
<div className="task-subtitle">{task.dateTime}</div>
</div>
<div className="task-options">
<button className="icon-button">×</button>
</div>
</div>
))}
</div>
</div>
</div>
);
}The key prop is task.id. The taskTitle and dateTime fields match the shape defined in the initial state.
Confirming in DevTools: open the Redux DevTools extension in Chrome and check the State tab. You should see tasks: [...] with the initial task objects. Add a new entry to initialTasks in data/tasks.js and the UI reflects it immediately on reload without touching any component code.
ExpanduseSelector reading the tasks slice of Redux state
How Re-rendering Works
useSelector runs the selector after every dispatched action. If the selected value changed (by reference), React schedules a re-render. If the value did not change, the component stays as-is. This is why returning new objects on every render from the selector is wasteful. Always select a specific slice, not the whole state.
// correct: reads one slice
const tasks = useSelector((state) => state.tasks);
// wrong: creates a new object reference every render → re-renders every time
const data = useSelector((state) => ({ tasks: state.tasks }));A note for when you reach the async chapters: right now state.tasks is an array. When Redux Thunk is added in Chapter 3, the state shape changes to { data: [], loading: false, error: '' }. At that point tasks.map(...) becomes tasks.data.map(...). This is intentional: the initial version uses simple state, and it expands once async is introduced.
The Essentials
useSelector(selector)subscribes the component to the Redux store. When the selected state changes, the component re-renders.store.getState()alone does not trigger re-renders.- The selector parameter name is arbitrary.
state.tasksreferences the key defined incombineReducers, not the reducer function name. - Return the minimum slice needed. Selecting a narrow slice reduces unnecessary re-renders. A selector that returns a full object literal re-renders on every dispatch regardless of whether the data changed.
Further Reading and Watching
- useSelector API docs: official reference including equality comparison behavior
- Deriving data with selectors: patterns for computing derived state efficiently
components/tasks/Tasks.json GitHub: the full Tasks component showing useSelector, useDispatch, and search together
Practice what you just read.
Keep reading