CodingNeed.

3 · Reliability and delivery · 30 MIN

Derived state and searchable lists

Values calculated from existing state usually belong in rendering.

The visible list depends entirely on the query and the source courses, so it can be computed during rendering. Keeping a second visibleCourses state and synchronizing it with an effect creates another value that can fall out of date. This small list does not need useMemo; measure before adding memoization. For larger catalogs, consider server search, pagination or virtualization as appropriate. Derived values should preserve the same matching rules across the count, list and empty state.

Check case-insensitive and trimmed search.

Read the example

import {useState} from 'react';
const courses=[{id:'js',title:'JavaScript'},{id:'py',title:'Python'},{id:'sql',title:'SQL'}];
export default function App() {
  const [query,setQuery]=useState('');
  const visible=courses.filter(c=>c.title.toLowerCase().includes(query.trim().toLowerCase()));
  return <main><h1>Find a course</h1><label>Search<input value={query} onChange={e=>setQuery(e.target.value)}/></label>
    <p role="status">{visible.length} matches</p><ul>{visible.map(c=><li key={c.id}>{c.title}</li>)}</ul>
    {!visible.length&&<p>Try another search.</p>}</main>;
}
Check the expected output
An empty query shows three courses; PY shows Python; xyz shows zero matches and an empty-state message.

Your challenge

Add a topic filter and derive the displayed count and list from both inputs without adding synchronization effects.

Solution cost: O(n × m) simple substring search across n titles of length up to m. time · O(n) matching references. space

Common trap

Effects used only to synchronize redundant state introduce unnecessary renders and stale intermediate values.

Study the project implementation
import {useState} from 'react';
const courses=[{id:'js',title:'JavaScript'},{id:'py',title:'Python'},{id:'sql',title:'SQL'}];
export default function App() {
  const [query,setQuery]=useState('');
  const visible=courses.filter(c=>c.title.toLowerCase().includes(query.trim().toLowerCase()));
  return <main><h1>Find a course</h1><label>Search<input value={query} onChange={e=>setQuery(e.target.value)}/></label>
    <p role="status">{visible.length} matches</p><ul>{visible.map(c=><li key={c.id}>{c.title}</li>)}</ul>
    {!visible.length&&<p>Try another search.</p>}</main>;
}

Further reading: Official documentation

Next lesson: Project · A timer with reliable cleanup

Essential cookies keep your account signed in. Optional analytics is not configured on this site. Your choice does not affect access to lessons.

Read the Privacy Policy. You can change this choice in the footer.