The previous lesson left two loose ends. The first is technical: the timer in StationAvailability returned an id that had to be stored somewhere so it could be canceled, and a plain variable won't do because it's lost on every render, while state would trigger a pointless re-render. The second goes further back, to 03-05: there it was mentioned that you can read a form field by reaching straight into the DOM node, and the explanation was postponed. useRef solves both, because they're really the same problem: it's a box that survives renders without being part of them. In this lesson you'll see its two uses — a reference to a DOM node and a mutable instance value —, when ref.current can be read and when it can't, how to pass refs to your own components in React 19 (where ref is now a normal prop), callback refs for dynamic elements, and the exact boundary between what deserves useRef and what deserves useState.
Contents
- The problem: remembering without re-rendering
useRef, the mutable boxuseState,useRef, and a plain variable: comparison table- Use 1: a reference to a DOM node
- Four real cases in CicloUrbano
- When
ref.currentis available - Use 2: a mutable instance value
- The golden rule: never read or write
currentduring render - Closing the debt from 03-05:
refversusFormData - Passing refs to your own components in React 19
- Callback refs for dynamic elements
- When NOT to use
useRef
- The problem: remembering without re-rendering
Picture a stopwatch on CicloUrbano's operator panel: it starts when you press "Start review" and stops when you press "Finish". You need to store the id that setInterval returns so you can cancel it later. Let's look at the two options you already know, and why neither works.
// ❌ Option A: a plain variable. It's lost on every render.
function ReviewTimer() {
const [seconds, setSeconds] = useState(0);
let intervalId = null; // born and lost with every render
function handleStart() {
intervalId = setInterval(() => setSeconds((prev) => prev + 1), 1000);
}
function handleStop() {
clearInterval(intervalId); // already null here: this stops nothing
}
}The first setSeconds triggers a render, the component function runs again, and intervalId is back to null. The stopwatch can't be stopped.
// ❌ Option B: state. It works, but it re-renders needlessly.
const [intervalId, setIntervalId] = useState(null);Now it does survive, but every setIntervalId forces a full re-render of the component to store a number that doesn't appear anywhere on screen. That's wasted work, and in large components it shows.
What we need is a third thing: something that persists like state but doesn't trigger renders like a plain variable. That's exactly useRef.
useRef, the mutable box
useRef, the mutable boxuseRef returns the very same object, render after render, with a single property:
And that's the whole trick. Nothing more to it. Three properties define its behavior:
- The returned object is stable. React never replaces it; on render 1 and on render 500 it's the same reference. That's why it doesn't need to go in an effect's dependency array (05-02).
currentis mutable, and you change it yourself. There's no updater function: you assign it directly,ref.current = something. React doesn't get involved.- Changing
currenttriggers no re-render at all. React doesn't even know it happened.
The stopwatch, solved:
// src/components/ReviewTimer.jsx
import { useState, useRef } from 'react';
function ReviewTimer({ bike }) {
const [seconds, setSeconds] = useState(0);
const intervalId = useRef(null); // ← the box that survives
function handleStart() {
if (intervalId.current !== null) return; // already running
intervalId.current = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
}
function handleStop() {
clearInterval(intervalId.current);
intervalId.current = null;
}
return (
<>
<p>Reviewing {bike.model}: {seconds}s</p>
<button type="button" onClick={handleStart}>Start review</button>
<button type="button" onClick={handleStop}>Finish</button>
</>
);
}
export default ReviewTimer;Notice the split: seconds is state because it's shown on screen and must trigger a re-render; intervalId is a ref because it's internal plumbing nobody looks at.
useState, useRef, and a plain variable: comparison table
useState, useRef, and a plain variable: comparison tablePlain variable (let x) |
useRef |
useState |
|
|---|---|---|---|
| Persists between renders? | ❌ No, it resets | ✅ Yes | ✅ Yes |
| Does changing it re-render? | ❌ No | ❌ No | ✅ Yes |
| How you change it | x = value |
ref.current = value |
setX(value) |
| Can it be read during render? | ✅ Yes | ⚠️ You shouldn't | ✅ Yes |
| Can it be written during render? | ✅ Yes | ❌ No | ❌ No |
| Does it belong in an effect's dependencies? | ✅ Yes (it's reactive) | ❌ No (it's stable) | ✅ Yes |
| What it's for | Local calculations within a render | Remembering without re-rendering; accessing the DOM | Data shown on screen |
The boundary comes down to one question: does this piece of data appear in what gets drawn? If yes, it's state. If not, it's probably a ref.
- Use 1: a reference to a DOM node
This is the most visible use. You declare the ref, pass it as the ref attribute of a JSX element, and React places the browser's real node there.
import { useRef } from 'react';
function FocusExample() {
const field = useRef(null); // 1. starts as null
function handleFocus() {
field.current.focus(); // 3. field.current IS the real <input>
}
return (
<>
<input ref={field} type="text" /> {/* 2. React puts the node in current */}
<button type="button" onClick={handleFocus}>Go to field</button>
</>
);
}The flow, precisely:
flowchart TD
A["Render: useRef(null) returns { current: null }"] --> B["JSX declares ref={field}"]
B --> C["Commit: React creates/updates the DOM node"]
C --> D["React assigns field.current = the real node"]
D --> E["Effects and handlers can now use field.current"]
E --> F{"Does the element unmount?"}
F -- "Yes" --> G["React sets field.current = null"]
style D fill:#dcfce7
style G fill:#fecaca
It's important to understand that React fills and clears current. You never assign it yourself in this use case: you only read it.
- Four real cases in CicloUrbano
Focusing the first field of the booking form
When the user presses "Book" on a card, the form appears, and it's a courtesy to have the cursor ready. It's also an accessibility requirement (03-06): someone navigating by keyboard shouldn't have to tab all the way there.
// src/components/BookingForm.jsx (excerpt)
import { useRef, useEffect } from 'react';
function BookingForm({ onCreateBooking, userId = 'usr-01' }) {
const bikeField = useRef(null);
useEffect(() => {
bikeField.current?.focus(); // on mount, focus goes to the first field
}, []);
return (
<form noValidate onSubmit={handleSubmit}>
<label htmlFor="bicicletaId">Bike</label>
<select id="bicicletaId" name="bicicletaId" ref={bikeField} …>
…
</select>
…
</form>
);
}The ?. isn't decorative: if the component renders with the form hidden, current will still be null, and a direct access would throw an error.
Scrolling the list to the selected bike
// src/components/BikeList.jsx (excerpt)
function BikeList({ bikes = [], stations = [], selectedId, onSelect, onBook }) {
const selectedCard = useRef(null);
useEffect(() => {
selectedCard.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, [selectedId]);
return (
<ul className={styles.list}>
{bikes.map((bike) => (
<li
key={bike.id}
ref={bike.id === selectedId ? selectedCard : null}
>
<BikeCard … />
</li>
))}
</ul>
);
}The trick is to pass the ref only to the element that matters and null to the rest. React only assigns current on that one, and the effect fires when selectedId changes.
Measuring an element
function FleetSummary({ fleet }) {
const container = useRef(null);
const [fitCount, setFitCount] = useState(0);
useEffect(() => {
const width = container.current.getBoundingClientRect().width;
setFitCount(Math.floor(width / 260)); // 260px per card
}, []);
return <div ref={container}>…</div>;
}Measuring is an operation that can only be done against the real DOM: React doesn't know about pixels. That's why it lives in an effect, after the commit.
Controlling a native <dialog>
Modal (04-02) used a <div> with role="dialog". The native <dialog> element offers trapped focus and closing on Escape for free, but it opens and closes with imperative methods, not attributes:
// src/components/Modal.jsx (variant using a native <dialog>)
import { useRef, useEffect } from 'react';
import styles from './Modal.module.css';
/**
* Props:
* - title (string, required)
* - open (boolean, required)
* - children (content)
* - onClose (function, required)
*/
function Modal({ title, open, children, onClose }) {
const dialog = useRef(null);
useEffect(() => {
const node = dialog.current;
if (!node) return;
if (open && !node.open) {
node.showModal(); // imperative method: there's no equivalent attribute
} else if (!open && node.open) {
node.close();
}
}, [open]);
return (
<dialog ref={dialog} className={styles.window} onClose={onClose}>
<h2>{title}</h2>
{children}
<button type="button" onClick={onClose}>Close</button>
</dialog>
);
}
export default Modal;This example sums up the philosophy: React declares what is on screen; when the browser only offers an imperative API, useRef is the bridge.
- When
ref.current is available
ref.current is available| Moment | Value of ref.current |
|---|---|
| During the first render | null (or whatever initial value you passed) |
| During any later render | The node from the previous render — don't use it |
In an effect (useEffect) |
✅ The current node, already in the DOM |
| In an event handler | ✅ The current node |
| After the element unmounts | null (React clears it) |
The practical consequence: you can't use the node during render. This doesn't work:
// ❌ current is null on the first render: TypeError
function BadPanel() {
const box = useRef(null);
const height = box.current.offsetHeight; // 💥
return <div ref={box}>…</div>;
}And even if it didn't fail, it would still be wrong: render must be pure (04-03), and reading the DOM makes it depend on external state. The place for that is an effect.
- Use 2: a mutable instance value
Besides nodes, current can hold anything at all. Three patterns you'll see all the time.
Storing a timer's id
You've already seen this in ReviewTimer. It's the canonical case: a piece of plumbing that has to survive and that nobody looks at.
Storing the previous value of a prop or a state value
Useful for animating a change, logging a transition, or comparing.
// src/components/DockCounter.jsx (excerpt)
import { useState, useRef, useEffect } from 'react';
function DockCounter({ freeDocks, station }) {
const previousDocks = useRef(freeDocks);
const [trend, setTrend] = useState('steady');
useEffect(() => {
if (freeDocks > previousDocks.current) setTrend('rising');
else if (freeDocks < previousDocks.current) setTrend('falling');
else setTrend('steady');
previousDocks.current = freeDocks; // written INSIDE the effect
}, [freeDocks]);
return (
<p>
{station.name}: {freeDocks} docks ({trend})
</p>
);
}What matters is where current gets written: inside the effect, meaning after the render. If it were written in the component body, the "previous" value would already have been overwritten before you got the chance to compare it.
Counting renders for debugging
function BikeCard({ bike, stationName, onSelect, onBook }) {
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
console.log(`BikeCard ${bike.id}: render #${renderCount.current}`);
});
…
}With useState this counter would be an infinite loop: incrementing it triggers a render, which increments it again. With useRef nothing happens, because it triggers no render. It's a common tool for investigating performance problems before opening the Profiler (08-05).
- The golden rule: never read or write
current during render
current during renderDuring render, a component must not read or write
ref.current. Only in effects and event handlers.
Writing during render breaks purity: two calls to the same function with the same arguments would stop producing the same result, and React reserves the right to call components more than once (StrictMode) or to abandon a render halfway through.
// ❌ writing during render: impure, and StrictMode counts it double
function Wrong() {
const visits = useRef(0);
visits.current += 1;
return <p>{visits.current}</p>;
}Reading during render is less serious but just as treacherous: since changing current doesn't re-render, whatever you paint with that value can go stale on screen with nothing to fix it. If a piece of data has to be visible, it's state.
The one tolerated exception is lazy ref initialization, when the initial value is expensive to build:
function MapPlayer() {
const instance = useRef(null);
if (instance.current === null) {
instance.current = createMap(); // runs exactly once; never reads a changing value
}
…
}It's accepted because the assignment happens exactly once and the result doesn't depend on the render.
- Closing the debt from 03-05:
ref versus FormData
ref versus FormData03-05 left open the comparison between the two ways of reading an uncontrolled field. Here they both are, side by side, on the same incident form in CicloUrbano.
// With FormData: reads ALL fields from their name attribute
function IncidentForm({ onRegister }) {
function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
onRegister({
bicicletaId: formData.get('bicicletaId'),
description: formData.get('description'),
urgent: formData.get('urgent') === 'on'
});
event.target.reset();
}
…
}
// With useRef: one ref per field
function IncidentForm({ onRegister }) {
const bikeField = useRef(null);
const descriptionField = useRef(null);
const urgentField = useRef(null);
function handleSubmit(event) {
event.preventDefault();
onRegister({
bicicletaId: bikeField.current.value,
description: descriptionField.current.value,
urgent: urgentField.current.checked
});
bikeField.current.focus(); // ← FormData simply can't do this
}
…
}| Criterion | FormData |
useRef |
|---|---|---|
| Reading every field on submit | ✅ One line, nothing to declare | ❌ One ref per field |
| Adding a new field | ✅ Just give it a name |
❌ You have to declare another ref |
| Reading a single stray field | Valid | Valid |
Acting on the node: focus, select(), scrollIntoView |
❌ Impossible | ✅ Its home turf |
| Integrating a library that needs the node | ❌ Impossible | ✅ Its home turf |
| File-type values | Valid (formData.get('photo')) |
Valid (field.current.files) |
Conclusion: FormData for reading, useRef for acting. And they combine perfectly well: reading with FormData on submit while keeping a single ref to the first field to give it back focus afterward is the cleanest split.
- Passing refs to your own components in React 19
Putting ref on a DOM element always works. Putting it on your own component is a different story, because <BikeCard /> isn't a node: it's a function.
In React 19 this is now solved on its own: ref is a normal prop on function components. All you have to do is receive it and forward it:
// src/components/TextField.jsx (React 19)
function TextField({ label, id, ref, ...rest }) {
return (
<p>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...rest} />
</p>
);
}
export default TextField;// Used from BookingForm
const dateField = useRef(null);
<TextField ref={dateField} id="startDate" label="Start date" type="datetime-local" />
// dateField.current is the <input> insideIn React 18 and earlier, ref didn't arrive as a prop: React intercepted it. You had to wrap the component in forwardRef, and you'll keep running into it constantly in existing code and in libraries:
// Older form, still functional in React 19 (deprecated but supported)
import { forwardRef } from 'react';
const TextField = forwardRef(function TextField({ label, id, ...rest }, ref) {
return (
<p>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...rest} />
</p>
);
});| React 18 | React 19 | |
|---|---|---|
Receiving ref in a function component |
forwardRef(fn) |
ref arrives as a normal prop |
Old code using forwardRef |
Mandatory | Still works, marked as deprecated |
One more piece, mentioned so you'll recognize it: useImperativeHandle lets a component expose to its parent a custom object instead of the DOM node (for example { focus, clear }), limiting what the parent can touch. It's a component-library tool, rarely seen in application code.
- Callback refs for dynamic elements
When the number of elements isn't known in advance — a ref for each bike card — you can't declare one useRef per element: that would break hooks rule 1 (04-04), which forbids calling them inside loops.
The solution is to pass a function as the ref attribute. React calls it with the node when it mounts:
// src/components/BikeList.jsx (excerpt with per-element refs)
import { useRef } from 'react';
function BikeList({ bikes = [], selectedId, ...rest }) {
const nodesById = useRef(new Map()); // A SINGLE ref, holding a map inside
function scrollToItem(id) {
nodesById.current.get(id)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
return (
<ul>
{bikes.map((bike) => (
<li
key={bike.id}
ref={(node) => {
nodesById.current.set(bike.id, node);
// React 19: the function can return a CLEANUP
return () => nodesById.current.delete(bike.id);
}}
>
<BikeCard bike={bike} {...rest} />
</li>
))}
</ul>
);
}Two things change in React 19:
- A
reffunction can return a cleanup function, just like an effect. React runs it when the element unmounts. Before, you had to work around it by checking whether the argument wasnull. - Precisely because of that, in React 19 a
reffunction must no longer return anything else. Watch out for the shorthandref={(node) => map.set(id, node)}, which returns the map by accident: you need braces.
// ❌ returns the Map: React would interpret it as a cleanup function
ref={(node) => nodesById.current.set(bike.id, node)}
// ✅ with braces: returns nothing, or an explicit cleanup
ref={(node) => { nodesById.current.set(bike.id, node); }}
- When NOT to use
useRef
useRefuseRef is tempting because it "works" and doesn't re-render. Here are the uses you should reject:
- Storing data that gets shown on screen. The symptom is unmistakable: you change
current, the screen doesn't notice, and you end up bolting on a fakeuseState(const [, forceUpdate] = useState(0)) to force a re-render. If you have to force a render, that data was state all along. - Replacing state "for performance". The cost of a well-designed render is negligible; the cost of an interface showing stale data isn't. Optimization has its own module (Module 8) and its own tools.
- Manipulating DOM that React controls. Changing
node.textContent, adding classes withclassList, or deleting children by hand collides with reconciliation (01-05): React can overwrite your changes on the next render, or fail when it doesn't find what it expected. You can touch what React doesn't manage — focus, scroll, selection, playing a video — but not the structure or the content. - As a cache for computations. That's what
useMemo(08-03) is for, and it also invalidates correctly when the inputs change.
// ❌ Antipattern: state disguised as a ref
const filter = useRef('todos');
function handleTypeChange(type) {
filter.current = type; // the list does NOT update; there's no render
}
// ✅ It's shown on screen → it's state
const [selectedType, setSelectedType] = useState('todos');As a final, related note, React offers useId to generate unique, stable identifiers for pairing a label with a field without collisions when a form repeats on the same page. It has nothing to do with the DOM directly, but it rounds out the accessibility toolkit from 03-06:
const dateId = useId();
<label htmlFor={dateId}>Start date</label>
<input id={dateId} type="datetime-local" />Common Mistakes and Tips
- Accessing
ref.currentduring render. On the first render it'snull, and the access throws aTypeError. Use effects or handlers, and?.as a safety net. - Forgetting that React clears
currenton unmount. A timer that uses the ref afterward may findnull. - Writing
currentin the component body. It breaks purity, and withStrictModecounters come out doubled. - Using
useReffor visible data. If you end up needing to force a render, that data was state. - Putting a ref in an effect's dependencies. The object is stable: it adds nothing and confuses whoever reads it.
- Accidentally returning a value from a callback ref. In React 19 it's interpreted as a cleanup function. Use braces.
- Modifying DOM that React manages. Only touch what React doesn't control: focus, scroll, selection, media.
- Using
forwardRefin new React 19 code. It's no longer necessary; save it for maintaining existing code. - Tip: when declaring a ref, name it after what it stores (
bikeField,intervalId,nodesById). A ref calledreftells the reader nothing. - Tip: if you're torn between
useStateanduseRef, ask yourself whether the screen should look different when that value changes. If yes, state; if no, ref.
Exercises
Exercise 1. This component is meant to count how many times the selected bike has changed and display it on screen, but it doesn't work. Explain why and fix it.
function SelectionCounter({ selectedBike }) {
const changes = useRef(0);
useEffect(() => {
changes.current += 1;
}, [selectedBike]);
return <p>You've changed bikes {changes.current} times</p>;
}Exercise 2. Write BikeSearch so that, on top of its current behavior, it offers a "Clear" button that empties the field and returns focus to the search field. Explain why the focus needs a ref and the text doesn't.
Exercise 3. BookingPanel must open inside a native <dialog> and close both via the "Cancel" button and the Escape key (which the browser handles on its own). Write the BookingDialog component that wraps BookingPanel, with props open, onClose, and children, making sure React's state and the <dialog>'s own state never fall out of sync.
Solutions
Solution 1.
The bug is one of category: changes is shown on screen, so it should be state. Writing to changes.current doesn't trigger any render, so the <p> keeps showing whatever value it was last painted with. The counter climbs internally while the screen stays at 0 (or at whatever number happened to be there when some other change caused an unrelated re-render — which is worse, because the bug becomes intermittent).
function SelectionCounter({ selectedBike }) {
const [changes, setChanges] = useState(0);
const isFirstRender = useRef(true); // THIS is a legitimate ref
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false; // the initial mount doesn't count as a change
return;
}
setChanges((prev) => prev + 1);
}, [selectedBike]);
return <p>You've changed bikes {changes} times</p>;
}The solution teaches both sides: changes becomes useState because it's visible; isFirstRender stays as useRef because it's internal plumbing that shouldn't re-render anything. And setChanges uses the functional form (05-01) because the new value depends on the previous one.
Solution 2.
// src/components/BikeSearch.jsx
import { useState, useRef } from 'react';
/**
* Props:
* - onSearch (function, required)
*/
function BikeSearch({ onSearch }) {
const [query, setQuery] = useState('');
const field = useRef(null);
function handleChange(event) {
setQuery(event.target.value);
onSearch(event.target.value);
}
function handleClear() {
setQuery('');
onSearch('');
field.current?.focus(); // acting on the node: only possible with the ref
}
return (
<div>
<input
ref={field}
type="search"
value={query}
onChange={handleChange}
aria-label="Search bikes by model"
/>
<button type="button" onClick={handleClear} disabled={query === ''}>
Clear
</button>
</div>
);
}
export default BikeSearch;Why the text doesn't need a ref and the focus does: the text is shown in the field, so it's state; React paints it with value={query}, and setting it to '' is all it takes to clear the field. Focus, on the other hand, isn't a piece of data React draws: it's a property of the browser. There's no JSX attribute that says "this field has focus right now"; there's only the node's focus() method. That's why you have to reach the real node. It's the exact line between declarative and imperative.
Solution 3.
// src/components/BookingDialog.jsx
import { useRef, useEffect } from 'react';
import styles from './BookingDialog.module.css';
/**
* Wraps BookingPanel in a native <dialog>.
* Props:
* - open (boolean, required)
* - onClose (function, required)
* - children (dialog content)
*/
function BookingDialog({ open, onClose, children }) {
const dialog = useRef(null);
useEffect(() => {
const node = dialog.current;
if (!node) return;
if (open && !node.open) {
node.showModal();
} else if (!open && node.open) {
node.close();
}
}, [open]);
return (
<dialog
ref={dialog}
className={styles.dialog}
onClose={onClose} // ← also fires on Escape
onCancel={(event) => { // Escape fires 'cancel' before 'close'
event.preventDefault(); // we prevent the native close directly…
onClose(); // …and let React's own state drive it
}}
aria-labelledby="booking-dialog-title"
>
<h2 id="booking-dialog-title">Confirm booking</h2>
{children}
</dialog>
);
}
export default BookingDialog;The keys to keeping things in sync: the !node.open and node.open checks avoid calling showModal() on a dialog that's already open — which throws a browser error — and close() on one that's already closed. And onClose is essential because the browser can close the dialog on its own with Escape: without that notification, React's state would keep saying open: true while the dialog was actually closed, and the next attempt to open it would do nothing. This is the typical problem when integrating an element with its own state: its changes have to be propagated back to React, just like a controlled field propagates its own with onChange (03-04).
Conclusion
useRef is a { current } box that survives renders and that, when changed, triggers none. Its two uses follow from that: storing a mutable instance value — a timer's id, a prop's previous value, a debugging counter — and holding a reference to a DOM node to do what React doesn't express declaratively: focusing a field, measuring an element, scrolling the list to the selected bike, or opening a <dialog>. You've seen when current is available (after the commit, never during render), the rule against reading or writing it while rendering, the comparison with FormData left open in 03-05 and now closed, how ref is now a normal prop in React 19 — with forwardRef reduced to legacy code —, and callback refs with cleanup for elements that appear and disappear. And above all, you now have a clear boundary: if the data is visible, it's state; if it isn't, it might be a ref.
With useState, useEffect, and useRef you now control what a component remembers and how it connects to the outside world. But there's still a debt outstanding from 04-01: when a piece of data has to travel from App down to a component buried five levels below, today you only know how to do it by passing props through every rung, even when the components in between never use them for anything. That prop drilling clutters every signature along the way and turns any change into a manual trek through half the application. React has a direct channel between an ancestor and any of its descendants, with no stops in between. The next lesson is The useContext Hook.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
