Skip to content

State-Driven UI in React

At the end of this class, you should be able to…

  • Explain how component state drives UI updates in React.
  • Implement conditional rendering and dynamic list rendering.
  • Compute and display derived state for responsive user feedback.

Additional Notes:

  • Emphasize using stable keys when rendering lists to avoid performance or reconciliation issues.
  • Demonstrate how React avoids direct DOM manipulation by re-rendering declaratively.
  • React 19’s improved rendering pipeline enhances async rendering - mention as a preview but stay with React 18 behaviour.
  • Reinforce that clear data flow and predictable state lead to reliable UI updates.

This lesson introduces:

  • What custom hooks are
  • Why custom hooks exist
  • How hooks can encapsulate reusable state logic
  • Persisting state using sessionStorage

We will extend the existing NAIT Student Resources example.

Specifically, we will persist the selected resource so that it remains selected after a page refresh.

Currently:

  • App owns selectedResource
  • Results notifies the parent when a resource is selected
  • Details renders conditionally based on selectedResource

If you refresh the page, the selected resource is lost.

We will now persist it.

A custom hook is simply:

  • A JavaScript function
  • Whose name starts with use
  • That calls one or more built-in hooks

Example pattern:

function useSomething() {
const [value, setValue] = useState(null);
return [value, setValue];
}

Custom hooks allow us to extract and reuse logic across components.

Create a new file:

src/hooks/useSelectedResource.js

Add the following:

import { useState } from 'react';
const STORAGE_KEY = 'selectedResource';
export function useSelectedResource() {
const [selectedResource, setSelectedResource] = useState(() => {
const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored) {
try {
return JSON.parse(stored);
} catch {
return null;
}
}
return null;
});
function updateSelectedResource(resource) {
setSelectedResource(resource);
if (resource === null) {
sessionStorage.removeItem(STORAGE_KEY);
} else {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(resource));
}
}
return [selectedResource, updateSelectedResource];
}
useState(() => { ... })

The function runs only on initial render.

This allows us to:

  • Read from sessionStorage once
  • Initialize state correctly
  • Avoid re-reading on every render

Open App.jsx.

Replace:

const [selectedResource, setSelectedResource] = useState(null);

With:

import { useSelectedResource } from './hooks/useSelectedResource';
const [selectedResource, setSelectedResource] = useSelectedResource();

No other changes are required.

  1. Select a resource
  2. Refresh the page
  3. The same resource should still be selected

Close the tab entirely and reopen it.

The selection will be cleared.

That is the difference between sessionStorage and localStorage.

  • State still lives in React
  • sessionStorage mirrors that state
  • The hook encapsulates persistence logic

The component does not need to know anything about storage.

We are writing to storage inside the setter.

This works because:

  • The setter runs synchronously
  • We are simply mirroring state

In the next lesson, we will discuss:

  • Side effects
  • Component lifecycle
  • Why useEffect exists
  • How to refactor this hook to separate state updates from persistence
  • Low cognitive load
  • Clear demonstration of custom hooks
  • No lifecycle discussion required
  • Immediate visible benefit (refresh persistence)
  • Clean separation of concerns
  • Side effects are inside the setter
  • Does not respond to external storage changes
  • Entire object is stored (not just id)
  • Hardcoded storage key

These tradeoffs will help motivate the next lesson.

  • Custom hooks wrap reusable state logic
  • Hooks can compose other hooks
  • Lazy state initialization
  • Separation of concerns
  • selectedResource is persisted across refresh
  • State is managed via a custom hook
  • App remains clean and readable
  • No useEffect is used yet
  1. Refactor the hook to store only resource.id instead of the entire object.
  2. Add a third return value that clears the stored selection.
  3. Create a second hook called useBoolean for toggling true/false values.
  1. Stage all changes:
Terminal window
git add -A
  1. Commit:
Terminal window
git commit -m 'Lesson 15 - custom hook for session storage'
  1. Push:
Terminal window
git push origin main