State-Driven UI in React
Learning Outcome Guide
Section titled “Learning Outcome Guide”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.
Lesson focus
Section titled “Lesson focus”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.
Connecting to prior lessons
Section titled “Connecting to prior lessons”Currently:
AppownsselectedResourceResultsnotifies the parent when a resource is selectedDetailsrenders conditionally based onselectedResource
If you refresh the page, the selected resource is lost.
We will now persist it.
Phase 1: What is a Custom Hook?
Section titled “Phase 1: What is a Custom Hook?”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.
Phase 2: Create the Hook File
Section titled “Phase 2: Create the Hook File”Create a new file:
src/hooks/useSelectedResource.jsAdd 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];}Important Concepts Introduced
Section titled “Important Concepts Introduced”Lazy Initialization
Section titled “Lazy Initialization”useState(() => { ... })The function runs only on initial render.
This allows us to:
- Read from
sessionStorageonce - Initialize state correctly
- Avoid re-reading on every render
Phase 3: Replace Existing State in App
Section titled “Phase 3: Replace Existing State in App”Open App.jsx.
Replace:
const [selectedResource, setSelectedResource] = useState(null);With:
import { useSelectedResource } from './hooks/useSelectedResource';
const [selectedResource, setSelectedResource] = useSelectedResource();No other changes are required.
Phase 4: Test the Behavior
Section titled “Phase 4: Test the Behavior”- Select a resource
- Refresh the page
- 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.
How This Works
Section titled “How This Works”- State still lives in React
sessionStoragemirrors that state- The hook encapsulates persistence logic
The component does not need to know anything about storage.
Why Not Use useEffect Yet?
Section titled “Why Not Use useEffect Yet?”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
useEffectexists - How to refactor this hook to separate state updates from persistence
Pros of This Approach
Section titled “Pros of This Approach”- Low cognitive load
- Clear demonstration of custom hooks
- No lifecycle discussion required
- Immediate visible benefit (refresh persistence)
- Clean separation of concerns
Limitations
Section titled “Limitations”- 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.
Key Concepts Reinforced
Section titled “Key Concepts Reinforced”- Custom hooks wrap reusable state logic
- Hooks can compose other hooks
- Lazy state initialization
- Separation of concerns
Assessment
Section titled “Assessment”selectedResourceis persisted across refresh- State is managed via a custom hook
Appremains clean and readable- No
useEffectis used yet
Student Exercise
Section titled “Student Exercise”- Refactor the hook to store only
resource.idinstead of the entire object. - Add a third return value that clears the stored selection.
- Create a second hook called
useBooleanfor toggling true/false values.
Push to your GitHub workbook repo
Section titled “Push to your GitHub workbook repo”- Stage all changes:
git add -A- Commit:
git commit -m 'Lesson 15 - custom hook for session storage'- Push:
git push origin main