Local vs Global State: Search Without Redux

Not everything belongs in Redux. The task search feature is a perfect example: filter the Redux state locally with useState and never dispatch a search action.

June 27, 20262 min read3 / 3

The task list reads from Redux and dispatches updates. The app is fully wired. Now add search, and make a deliberate decision about where the search state lives.

The answer: in useState, not Redux.

The Rule: Global Only When Shared

Redux exists for state that multiple components need to read or update. If state is needed in exactly one component, local useState is correct.

The search query is typed in Tasks, filtered in Tasks, and displayed in Tasks. No other component cares about it. It never needs to leave the component.

Dispatching a SET_SEARCH_QUERY action to the store would add an action type, a reducer case, a useSelector call, and a useDispatch call, for no benefit. The complexity is real, the gain is zero.

Add a search state property in Tasks.js:

JSX
const [search, setSearch] = useState('');

Bind it to the search input:

JSX
<div className="search-box"> <i className="fas fa-search" /> <input type="text" placeholder="Search tasks..." value={search} onChange={(e) => setSearch(e.target.value)} /> </div>

Then derive filteredTasks from the Redux state and the search string:

JSX
const tasks = useSelector((state) => state.tasks); const filteredTasks = tasks.filter((task) => task.taskTitle.toLowerCase().includes(search.toLowerCase()) );

Render from filteredTasks, not from tasks:

JSX
{filteredTasks.map((task) => ( <div key={task.id} className="task"> {/* ... */} </div> ))}

Typing "design" shows only tasks whose title contains "design", case-insensitively. Clearing the input shows all tasks again.

No dispatch. No action type. No reducer change. The Redux state from the store is read once via useSelector, then filtered locally. The store never knows the user is searching.

Search filters Redux state locally without touching the store ExpandSearch filters Redux state locally without touching the store

Why This Matters

This pattern is not obvious. The instinct when using Redux is to put everything through the store. That instinct leads to overly complex applications where trivial UI interactions generate action logs in DevTools, and reducers grow with cases that will never need to be shared.

A useful mental test before creating a Redux state slice: "Will another component ever need to read or update this value?" If the answer is no, keep it in useState.

Redux state: the task list, user authentication, shopping cart, notification queue. Local state: form inputs, modal visibility, search queries, tooltip hover state.

The Essentials

  1. Search state belongs in useState. The search query is used in one component. Dispatching a Redux action for it adds complexity with no gain.
  2. Derive filtered data locally. tasks.filter(...) runs inside the component, reads from useSelector, and never touches the store.
  3. The test: before adding something to Redux, ask whether any other component needs it. If not, useState is correct.

Further Reading and Watching

Practice what you just read.

Add Search Without Redux
1 exercise