131. Module Introduction & Starting Project
-
Focus: React Refs and Portals—optional but powerful tools for specific problems.
-
Refs:
-
Provide direct access to DOM elements.
-
Store mutable values that shouldn’t trigger re-renders (not state).
-
Let components expose an API (e.g., callable functions) to other components.
-
-
Portals:
-
Render elements in a different place in the DOM than where they appear in JSX (e.g., modals/overlays).
-
-
Demo project:
-
Build a timer challenge game using Refs, Portals, components, and state.
-
Starter projects available (local and CodeSandbox).
-
Initial setup includes a basic App and Player component; the input’s “Set Name” button doesn’t work yet—fixing it will be the first Refs example.
-
132. Repetition - Managing User Input with State (Two-Way-Binding)
The text walks through implementing a React “player” component that
replaces “unknown entity” with a user-entered name
using useState (no refs):
-
Import
useStateand create stateenteredPlayerName(initially''). -
Add
handleChangeand wire it to the input’sonChangeto storeevent.target.valuein state. -
Make the input a controlled component by binding
value={enteredPlayerName}. -
Add a second state, e.g.
submitted(initiallyfalse), plushandleClickon the button to setsubmitted(true). -
Conditionally render after “Welcome” via a ternary:
-
if
submitted→ showenteredPlayerName -
else → show
"unknown entity"
-
-
Notes a behavior issue: after submitting, the displayed name updates on every keystroke because it uses
enteredPlayerName. To avoid that, you can resetsubmittedtofalseinhandleChange, but that causes the UI to jump back to “unknown entity” while typing. -
Conclusion: this is a case where the state-based approach becomes verbose/awkward for simply reading the input value on button click, motivating the use of refs to simplify the component.
134. Introducing Refs - Connecting & Accessing HTML Elements via Refs
-
What a ref is
-
A
refin React is a React-managed value object (created withuseRef) that holds a.currentproperty. -
You typically use refs to get direct access to a DOM element (or to hold a mutable value that persists across renders).
-
-
How to create and connect a ref
-
Create:
const inputRef = useRef() -
Connect to element in JSX:
<input ref={inputRef} /> -
The connected DOM element is available as
inputRef.currentafter mount.
-
-
How this simplifies the component
-
Instead of updating component state on every keystroke with an
onChangehandler (controlled input), attach a ref to the input and readinputRef.current.valueonly when you need it (for example on button click). -
This removes the need for the per-keystroke handler, the input value prop, and intermediate submitted state — fewer props and less code.
-
Example flow:
-
useRefto createinputRef -
On button click:
const value = inputRef.current.value;setEnteredPlayerName(value) -
Render
enteredPlayerName(with a fallback like 'Unknown' if empty)
-
-
-
Small note about fallback operators
-
The speaker mentioned "two question marks" (
??) as a shortcut for fallback.??is the nullish coalescing operator (falls back only onnull/undefined). If you want to treat empty string as fallback, use||instead.
-
Short example (conceptual)
-
const inputRef = useRef() -
<input ref={inputRef} /> -
function handleClick() { setEnteredPlayerName(inputRef.current.value) } -
Remove
onChange,valueprop, and any submitted state
Result: you update state only when needed (on click), read the real DOM input value via ref, and keep the component leaner.
135. Manipulating the DOM via Refs
Refs can be used to reset an input after setting a name by imperatively clearing it (e.g., playerName.current.value = ''). This works, but it’s less “React-like” because React encourages declarative UI updates rather than direct DOM manipulation. Reading a value via a ref is relatively mild, but changing the DOM through the ref is imperative. For simple, isolated cases like clearing an input not tied to other state, this can be acceptable and reduces code—but refs shouldn’t become a general tool for manipulating many DOM values, since that goes against React’s intended approach.
136. Refs vs State Values
-
Refs can hold a reference to a DOM element (e.g., an input) and let you read its current value directly.
-
On the first render the
refconnection (ref.current) is not yet established, soref.currentcan be undefined. Accessingref.current.valuethen causes an error unless you checkref.currentis truthy first. -
Crucially, changing a
refdoes NOT cause the component function to re-run (no re-render). That’s why replacing state with a ref-only approach meant the UI didn’t update when the input value changed. -
State updates (via
setState) do cause the component to re-execute and re-render, so state should be used for values that must be reflected in the UI. -
Use refs for behind-the-scenes values or direct DOM access where re-rendering is not required. Use state for any values that need to update the UI.
Both concepts are important and serve different purposes.
137. Adding Challenges to the Demo Project
A new reusable TimerChallenge React component is added to build a timer-based challenges app and set up a use case for refs.
-
Create
TimerChallenge(default export) that renders a<section class="challenge">(styling comes from an existing CSS file). -
The component takes props:
-
titleto display in an<h2>. -
targetTimeto display in a paragraph with classchallenge-time, formatted as “X second(s)” with the “s” added only whentargetTime > 1.
-
-
Add UI elements for timer behavior (to be wired up later with state):
-
A button that should toggle between “Start Challenge” and “Stop Challenge” depending on whether the timer is running.
-
A status paragraph that conditionally shows “Time is running…” (and applies an “active” class) or “Timer inactive”.
-
-
Use this component inside
Appwithin the#challengesdiv by importing it and rendering four instances:-
“Easy” (1 second)
-
“Not easy” (5 seconds)
-
“Getting tough” (10 seconds)
-
“Pros only” (15 seconds)
-
-
At this stage the buttons don’t work yet; next steps will add state and then refs to implement the timer logic.
138. Setting Timers & Managing State
-
Goal: start a countdown when the "Start Challenge" button is pressed and detect when it expires.
-
Implementation:
-
Create
handleStartthat uses JavaScriptsetTimeout(not React-specific) withdelay = targetTime * 1000(convert seconds → ms). -
Use
useStatefor two pieces of state:-
timerExpired(initial false) — set to true inside the timeout callback; when true show a "You lost" paragraph. -
timerStarted(initial false) — set to true immediately when starting the timer; used to change UI (button text from "Start Challenge" → "Stop", add an active class, and show "Time is running" vs "Timer inactive").
-
-
Connect
handleStartto the buttononClick.
-
-
Next step: implement
handleStopto clear the timer. To access the timeout ID fromhandleStop, store it in a ref (useRef) so you can callclearTimeouton it.
139. Using Refs for More Than DOM Element Connections
-
To stop a timer you call
clearTimeoutwith the ID returned bysetTimeout— so you must keep a reference to that ID. -
Storing the timer ID in a plain variable inside the component fails because the component function re-executes on state changes and that variable is recreated. The
handleStopwill see a different/unstored value. -
Moving the variable outside the component makes it persist across renders, but then it’s shared by all component instances. Starting one instance overwrites the ID of another, so stopping one can fail to clear the other’s timer.
-
The correct solution is to use a ref (
useRef):-
Create a ref in the component:
const timerRef = useRef(null). -
Save the ID:
timerRef.current = setTimeout(…). -
Clear it:
clearTimeout(timerRef.current). -
A ref is instance-specific (not shared across component instances), persists across re-renders, and changing
.currentdoes not trigger a rerender.
-
-
Use refs for values that must persist across renders but do not directly drive UI updates (timers, mutable handles, etc.).
140. Adding a Modal Component
-
Added a new
ResultModalcomponent to show a pop-up when the timer ends (won or lost) and later to show the score (based on how close to expiry the timer was). -
ResultModalreturns the native HTML<dialog>element with anh2for the result, a paragraph showing the target time (and later the seconds left), a form withmethod="dialog"and a close button so the dialog can be closed without extra JS, and aclassNamefor styling. -
TimerChallengewill import and renderResultModalconditionally (for example when the timer expired). The component was placed alongside the existing section by wrapping the JSX in a fragment, and the old inline conditional paragraph was removed. -
Important detail: simply adding the
openattribute to<dialog>makes it visible but does not show the built-in backdrop. To get the backdrop you must open the dialog programmatically (e.g., using thedialog.showModal()API), which requires accessing the dialog element via a ref from the parent component (TimerChallenge).
141. Forwarding Refs to Custom Components
-
Goal: access the native element inside
ResultModalfrom theTimerChallengecomponent using a ref soTimerChallengecan calldialog.showModal()when the timer expires. -
Modern React (≥19) approach:
-
Create a ref in
TimerChallenge(e.g.,dialog). -
Pass that ref as a prop to (or any prop name you choose).
-
In
ResultModalassign that received ref to the dialog element’s ref attribute sodialog.currentpoints to the element. -
Call
dialog.current.showModal()fromTimerChallengewhen needed. The dialog element’sshowModal()is a standard browser API. -
Keep
ResultModalrendered (it can be invisible) so the dialog element exists.
-
-
Older React (<19) issue:
-
Older React versions do not allow accepting a ref as a normal prop and will error if you try.
-
-
Compatible solution for older React:
-
Wrap the
ResultModalfunction withReact.forwardRef. -
forwardRefreturns an adjusted component that receives props as the first parameter and the forwarded ref as a second parameter. -
Use that second parameter to attach the ref to the dialog element and export the wrapped component.
-
This
forwardRefpattern is optional in modern React but required when working with older versions.
-
-
Practical note: both approaches work;
forwardRefis commonly seen in codebases and necessary for compatibility with older React.
142. Exposing Component APIs via the useImperativeHandle Hook
Summary — exposing a stable, callable API from a component with
useImperativeHandle
Problem
-
Calling DOM methods (e.g.,
dialog.showModal()) from a parent via a ref couples the parent to the child’s internal markup. If the child changes (dialog → div), the parent breaks.
Solution
-
Let the child expose a stable function-based API via
useImperativeHandle(optionally combined withforwardRef). The parent calls that public method (e.g.,open()) instead of calling internal DOM methods.
How it works (implementation outline)
-
In the child (
ResultModal):-
Create an internal ref for the dialog element:
const dialog = useRef(). -
Wrap the component with forwardRef (or accept a ref prop in React 19+).
-
Call
useImperativeHandle(ref, () ⇒ ({ open: () ⇒ dialog.current.showModal() })). -
Attach dialog to the dialog element: …
-
-
In the parent:
-
Hold a ref and pass it to .
-
Call
dialogRef.current.open()— this calls the child’s exposed open method.
-
Notes / benefits
-
Decouples parent from child implementation details; the child can change internals while keeping the same exposed API.
-
Prefer props/state for normal interaction;
useImperativeHandlefor imperative APIs (like showing a modal). -
forwardRef is needed in older React versions; with React 19+ you can accept the ref as a prop instead.
143. More Examples - When To Use Refs & State
-
Replace
setTimeoutwithsetIntervalso you can track remaining time continuously (example uses 10 ms intervals; choose a sensible value for performance). -
Manage a single piece of state:
timeRemaining(in milliseconds). Initialize it totargetTime * 1000. -
In the interval callback update state with the functional form:
setTimeRemaining(prev ⇒ prev - 10). -
Keep the interval id in a ref (not state or a plain variable) so you can clear it later with
clearInterval(ref.current). -
Derive whether the timer is active from
timeRemaining(e.g., active whentimeRemaining > 0 && timeRemaining < targetTime*1000) and use that derived value to conditionally render/buttons and behavior. -
Detect expiration in the component body (if
timeRemaining ⇐ 0): clear the interval, reset timeRemaining to the initial value, and open the result modal. Note the caution about calling state setters inside the component body — it’s safe here because the if condition won’t be true after updating state. -
Also clear the interval and open the same modal in
handleStop(manual stop). The modal open call is the same in both cases; the message can differ (you won if you stopped it manually, you lost if it expired). -
Next necessary step: pass the correct information (win/loss and a score computed from remaining time) into the modal.
Key code ideas:
intervalIdRef.current = setInterval(...);
setTimeRemaining(prev => prev - 10);
clearInterval(intervalIdRef.current);
dialogRef.current.open();
144. Sharing State Across Components
-
Replace the old result prop with a
remainingTimeprop on the result modal and pass the timer’stimeRemainingstate to it. -
In
ResultModal:-
Destructure
remainingTime. -
Compute a
userLostboolean:userLost = remainingTime ⇐ 0. -
Conditionally show an "You lost" message when
userLostis true. -
Format
remainingTime(stored in milliseconds) to seconds by dividing by 1000 and usingtoFixed(2)for a two-decimal display, and show that value.
-
-
Remove the obsolete result prop.
-
Fix a bug:
TimerChallengewas resettingtimeRemainingback to the initial value exactly when the timer expired, so the modal saw a positive time and couldn’t detect the loss. Solution:-
Move the reset logic into a separate
handleResetfunction. -
Pass
handleResetintoResultModalas anonResetprop. -
In
ResultModal, destructureonResetand attach it to the form’sonSubmitso the timer is reset when the modal is closed/submitted.
-
-
Outcome: when the timer expires the modal now correctly shows "You lost" and 0.00 seconds, and closing the modal triggers the reset so the timer can be restarted.
-
Next step: show a success message and calculate/display a score when the timer is stopped in time.
145. Enhancing the Demo App ResultModal
The text explains how to compute and display a game “score” in a Result Modal:
-
Calculate score using the formula:
score = (1 − (remainingTime / (targetTime * 1000))) * 100, then round it to get a value between 0 and 100. -
Only show “Your score: …” if the user didn’t lose.
-
Convert
targetTimefrom seconds to milliseconds by multiplying by 1000, sinceremainingTimeis in milliseconds. -
Add parentheses so the
targetTime * 1000calculation happens before division. -
After saving, stopping the timer in time shows the score and remaining time; getting closer to zero remaining time yields a better score, and this works across all challenge durations.
147. Introducing & Understanding Portals
React portals let you render a component’s JSX in a different place in the DOM than where the component is used in the React component tree. This is useful for overlays/modals: although they’re triggered inside a nested component (e.g., TimerChallenge), it’s often better for accessibility and styling to place the modal markup near the <body> root (so it isn’t deeply nested or potentially clipped/hidden).
To create a portal, import createPortal from react-dom (not react). Wrap the JSX you want to “teleport” with createPortal(jsx, targetElement), where targetElement is an existing DOM node in index.html (e.g., a <div id="modal"></div>). You typically select it with document.getElementById('modal').
After applying this, the modal behaves the same visually, but inspecting the DOM shows the dialog rendered inside the #modal container instead of nested inside the component’s normal DOM location—common for modals and other overlays.