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 useState and create state enteredPlayerName (initially '').

  • Add handleChange and wire it to the input’s onChange to store event.target.value in state.

  • Make the input a controlled component by binding value={enteredPlayerName}.

  • Add a second state, e.g. submitted (initially false), plus handleClick on the button to set submitted(true).

  • Conditionally render after “Welcome” via a ternary:

    • if submitted → show enteredPlayerName

    • 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 reset submitted to false in handleChange, 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 ref in React is a React-managed value object (created with useRef) that holds a .current property.

    • 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.current after mount.

  • How this simplifies the component

    • Instead of updating component state on every keystroke with an onChange handler (controlled input), attach a ref to the input and read inputRef.current.value only 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:

      • useRef to create inputRef

      • 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 on null/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, value prop, 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 ref connection (ref.current) is not yet established, so ref.current can be undefined. Accessing ref.current.value then causes an error unless you check ref.current is truthy first.

  • Crucially, changing a ref does 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:

    • title to display in an <h2>.

    • targetTime to display in a paragraph with class challenge-time, formatted as “X second(s)” with the “s” added only when targetTime > 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 App within the #challenges div 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 handleStart that uses JavaScript setTimeout (not React-specific) with delay = targetTime * 1000 (convert seconds → ms).

    • Use useState for 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 handleStart to the button onClick.

  • Next step: implement handleStop to clear the timer. To access the timeout ID from handleStop, store it in a ref (useRef) so you can call clearTimeout on it.

139. Using Refs for More Than DOM Element Connections

  • To stop a timer you call clearTimeout with the ID returned by setTimeout — 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 handleStop will 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 .current does 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 ResultModal component 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).

  • ResultModal returns the native HTML <dialog> element with an h2 for the result, a paragraph showing the target time (and later the seconds left), a form with method="dialog" and a close button so the dialog can be closed without extra JS, and a className for styling.

  • TimerChallenge will import and render ResultModal conditionally (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 open attribute 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 the dialog.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 ResultModal from the TimerChallenge component using a ref so TimerChallenge can call dialog.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 ResultModal assign that received ref to the dialog element’s ref attribute so dialog.current points to the element.

    • Call dialog.current.showModal() from TimerChallenge when needed. The dialog element’s showModal() is a standard browser API.

    • Keep ResultModal rendered (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 ResultModal function with React.forwardRef.

    • forwardRef returns 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 forwardRef pattern is optional in modern React but required when working with older versions.

  • Practical note: both approaches work; forwardRef is 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 with forwardRef). The parent calls that public method (e.g., open()) instead of calling internal DOM methods.

How it works (implementation outline)

  1. 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: …​

  2. 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; useImperativeHandle for 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 setTimeout with setInterval so 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 to targetTime * 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 when timeRemaining > 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 remainingTime prop on the result modal and pass the timer’s timeRemaining state to it.

  • In ResultModal:

    • Destructure remainingTime.

    • Compute a userLost boolean: userLost = remainingTime ⇐ 0.

    • Conditionally show an "You lost" message when userLost is true.

    • Format remainingTime (stored in milliseconds) to seconds by dividing by 1000 and using toFixed(2) for a two-decimal display, and show that value.

  • Remove the obsolete result prop.

  • Fix a bug: TimerChallenge was resetting timeRemaining back 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 handleReset function.

    • Pass handleReset into ResultModal as an onReset prop.

    • In ResultModal, destructure onReset and attach it to the form’s onSubmit so 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 targetTime from seconds to milliseconds by multiplying by 1000, since remainingTime is in milliseconds.

  • Add parentheses so the targetTime * 1000 calculation 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.