4. Context, Stores & Custom Hooks
Goal for this page: stop passing the same props through many layers ("prop drilling"), move shared state into a Store, keep logic in a controller, and start reusing logic with custom hooks. This is the page where DevDirectory starts to really look like an AquaGen feature.
Step 4.1 — A store with useContext
What & why: Some state is needed all over the app — a theme, the logged-in user, a list of
favorites. Context lets any component read that state without threading it through every
parent. In AquaGen this lives in a Store file: a Context provider that holds state with
useState and exposes it (that's exactly what AppStore and each feature store are).
Learn:
Your task: Add a Favorites feature. Create src/store/FavoritesStore.jsx: a Context
provider that holds a list of favorited item IDs in useState and exposes add, remove, and
the current list. Put a ⭐ button on each Card and add a "Favorites" page that reads from the
store. Confirm favoriting on the list page shows up on the Favorites page without passing props
between them.
Step 4.2 — Move logic into a controller
What & why: A store should hold state; the logic that decides what goes into it belongs in
a controller. In AquaGen, controller/ sits between dataSource/ (raw API) and the
store/ + components/ — it fetches, shapes, and combines data so components stay simple.
Learn:
- React: Keeping Components Pure (why logic shouldn't live in the UI)
- Separation of concerns (Wikipedia) (the underlying idea)
Your task: Create src/controller/itemsController.js. Move any data-shaping logic out of
your components into it — e.g. a function that calls getItems() from your dataSource, sorts
or filters the result, and returns exactly what the list page needs. Your component should now
just call the controller and render.
useReducerWhen a single store manages many related fields, some teams reach for
useReducer instead of several useState calls.
It's good to know — but AquaGen's stores use Context + useState, so that's the pattern to
practice here. Treat useReducer as background reading, not a requirement.
Step 4.3 — Extract logic into custom hooks
What & why: When two components need the same logic (e.g. "fetch this URL, track
loading/error"), copy-pasting it violates DRY. A custom hook is just a function starting
with use that packages reusable stateful logic. AquaGen keeps shared hooks in
libs/shared/src/hooks.
Learn:
Your task:
- Extract your fetch + loading + error pattern into a single custom hook, e.g.
useFetch(url)(put it insrc/hooks/). Use it on both the list and detail pages. - Write a second hook
useDebounce(value, delay)and use it to delay firing the search request while the user is still typing.
DevDirectory now has a store/ (Context), a controller/, a dataSource/, reusable hooks/,
and components/ — the same layers as a real AquaGen feature. One page left: make it clean and
production-shaped.
Next: → Best practices & workflow