Synchronizing with effects
Keep React components in sync with things outside React using useEffect: dependencies, cleanup functions, and why Effects run twice in development.
- 01Write an EffectUse
useEffectto connect, subscribe, or start a timer after React commits, and tell it apart from an event handler. - 02Control when it runsPick the dependency array the code needs, and predict when React runs the Effect and its cleanup.
- 03Fix, don't hide, the second runExplain why StrictMode runs Effects twice in development, and write the cleanup that makes it harmless.
A chat room that connects when it appears
A support chat has to connect to the chat server while it's on the screen, and disconnect when it goes. No click causes the connection: the chat being there is the reason. React has a place for code like this, called an Effect. Here is a chat room with one. The chat.js tab is a pretend server that logs every connection and counts how many are open. Click Open the chat, watch the console under the result, then click Close the chat.
import { useEffect, useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom() { useEffect(() => { const connection = createConnection("general"); connection.connect(); return () => connection.disconnect(); }, []); return <h3>Welcome to the general room</h3>;} export default function App() { const [open, setOpen] = useState(false); return ( <div> <button onClick={() => setOpen(!open)}>{open ? "Close the chat" : "Open the chat"}</button> {open && <ChatRoom />} </div> );}Line 1 imports useEffect. Lines 5 to 9 call it with a function, and that function is the Effect: line 6 creates a connection to the general room, and line 7 connects. The function it returns on line 8 is the cleanup, which disconnects. The [] on line 9 says when to run it, which you'll meet below. App shows ChatRoom only while open is true (line 20).
Opening the chat logs Connected to general. Open connections: 1, then a disconnect and a second connect, ending at one connection. The extra pair is a development-only check that the last section of this lesson explains. Closing the chat logs Disconnected from general. Open connections: 0: React ran the cleanup because ChatRoom left the page.
ChatRoom, puts its <h3> on the page, and only then runs the Effect. The Effect reaches outside React, to the chat server. When ChatRoom goes away, React runs the cleanup, which disconnects.A shop plays the radio station written on a sign by the till. Whoever opens up switches the radio on. If the manager changes the sign, someone switches the old station off and tunes in the new one. At closing time, the radio goes off. Nobody plays the radio because a customer asked; it plays because the shop is open.
- In real life: The shop opens, and someone switches the radio on
- In React: The component appears, and React runs the Effect
- In real life: The sign on the wall says which station to play
- In React: The dependencies, like
roomId - In real life: The sign changes: switch the old station off, tune in the new one
- In React: A dependency changes: the cleanup runs, then the Effect again
- In real life: The shop closes: switch the radio off
- In React: The component goes away: the cleanup runs
An Effect is code that runs after React commits a render, to keep a component in sync with a system outside React: a server connection, a timer, a browser event, or a widget that isn't React. Write it with useEffect, list what it reads as its dependencies, and return a cleanup that undoes it.
Rendering code, event handlers, and Effects
Code inside a component sits in one of three places, and each runs at a different time. Rendering code at the top of the component works out the JSX from props and state; it must stay pure, a calculation that changes nothing. Event handlers, from Responding to events, run when the person does something, and they may change things: sending a message is caused by a click on Send. Effects run because the component is on the page.
How to read the table: each row is one place for code, when React runs it, and what it's for.
| Where the code is | When it runs | What it's for |
|---|---|---|
| Rendering code, at the top of the component | While React works out the JSX, on every render. | Calculates the JSX from props and state. Changes nothing. |
An event handler, like handleSend | When the person clicks, types, or submits. | Does what the person asked for: send a message, buy a product. |
An Effect, inside useEffect | After React commits a render to the page. | Keeps the component in sync with something outside React while it's on screen. |
Why not connect right in the component body? Try it: this version connects while rendering, a bug on purpose. Type a few letters in the box and watch the console.
import { useState } from "react";import { createConnection } from "./chat.js"; export default function App() { const [message, setMessage] = useState(""); // A bug on purpose: connecting while rendering. const connection = createConnection("general"); connection.connect(); return ( <label> Message <input value={message} onChange={(e) => setMessage(e.target.value)} /> </label> );}Lines 8 and 9 run on every render. The first render logs two connections (StrictMode renders twice in development), and every letter you type renders App again, so the count climbs by two per key. Nothing ever disconnects, because rendering code has no way to know when to stop. Worse, React may call a component many times before it commits, or throw a render away. Effects fix all of this: they run after the commit, at most once per committed render, and they come with a cleanup.
Writing an Effect with useEffect
Call useEffect at the top level of a component, like useState, and pass it a function. React doesn't run that function during the render. It saves it, puts the render on the page, and runs it afterwards. You can see the order with a ref from Manipulating the DOM with refs. Look at the console, then click Add a cup.
import { useEffect, useRef, useState } from "react"; export default function App() { const [cups, setCups] = useState(0); const headingRef = useRef(null); useEffect(() => { console.log("Effect: the page says", headingRef.current.textContent); }); console.log("Render: the page says", headingRef.current?.textContent ?? "nothing yet"); return ( <div> <h3 ref={headingRef}>Cups: {cups}</h3> <button onClick={() => setCups(cups + 1)}>Add a cup</button> </div> );}The Effect is written first, on lines 7 to 9, but line 11 prints first: Render: the page says nothing yet, because during the first render the <h3> on line 15 doesn't exist. Then the Effect prints Effect: the page says Cups: 0. By the time it runs, React has committed the heading, and the ref holds it. Each line shows a small 2: StrictMode renders twice and runs the new Effect twice in development.
Click Add a cup. The render still sees Cups: 0 on the page, and the Effect sees Cups: 1. This Effect has no second argument, so it runs after every render.
You may read that Effects run "after the paint", once the browser has drawn the new screen. Treat that as a rule of thumb. React only promises that Effects run after the commit. After a click or a key press, React runs them straight after the commit, before the browser paints. After other updates, like a timer, React runs them a little later, so the browser can paint first if a new frame is due. Layout effects and timing, later in this stage, shows when that difference matters, and The commit phase shows it in React's code.
"After every render" has a trap. An Effect that sets state with no array causes a render, which runs the Effect, which sets state again, forever:
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});Line 4 changes count after every render, and each change causes another render. React warns in the console with Maximum update depth exceeded. If an Effect only sets state from other state, with nothing outside React involved, it usually shouldn't be an Effect at all; the next lesson, You might not need an Effect, shows what to write instead.
Dependencies decide when an Effect runs again
Reconnecting after every render is wasteful: typing a message shouldn't reconnect the chat. The second argument to useEffect, the dependency array, lists the values the Effect reads from the component. After each render, React compares each one with its value from the last render, using Object.is, and skips the Effect when none changed. Type a message, then click Switch room.
import { useEffect, useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom({ roomId }) { const [message, setMessage] = useState(""); useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return ( <label> Message to {roomId}{" "} <input value={message} onChange={(e) => setMessage(e.target.value)} /> </label> );} export default function App() { const [roomId, setRoomId] = useState("general"); const [open, setOpen] = useState(true); return ( <div> <button onClick={() => setRoomId(roomId === "general" ? "travel" : "general")}>Switch room</button> <button onClick={() => setOpen(!open)}>{open ? "Close the chat" : "Open the chat"}</button> <p>{open ? <ChatRoom roomId={roomId} /> : "The chat is closed."}</p> </div> );}ChatRoom now takes the room as a prop (line 4), and the Effect connects to it (line 8). Line 11 lists [roomId], because the Effect reads roomId. Typing in the box on line 16 changes message and renders ChatRoom again, but roomId is the same, so the console stays quiet. Switch room (line 27) changes it: React runs the old cleanup, Disconnected from general, then the Effect, Connected to travel. Still one connection open.
Object.is. Typing leaves roomId the same, so the Effect is skipped. Switching rooms changes it, so the old cleanup runs, then the Effect.You don't choose the dependencies; the code does. Every prop, state variable, or value worked out from them that the Effect reads must be in the array. React's linter rule, react-hooks/exhaustive-deps, checks this and warns about any you leave out. Two kinds of value can be left out because they never change: a ref from useRef, and a set function from useState. To make an Effect run less often, change its code, not the list; later lessons in this stage show how.
Try the three kinds of array in the playground. Pick one, then type a letter, switch rooms, and close and reopen the chat. Switch between development and production to see what StrictMode adds.
import { useEffect, useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom({ roomId }) { const [message, setMessage] = useState(""); useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return ( <label> Message to {roomId}{" "} <input value={message} onChange={(e) => setMessage(e.target.value)} /> </label> );} export default function App() { const [roomId, setRoomId] = useState("general"); const [open, setOpen] = useState(true); return ( <div> <button onClick={() => setRoomId(roomId === "general" ? "travel" : "general")}>Switch room</button> <button onClick={() => setOpen(!open)}>{open ? "Close the chat" : "Open the chat"}</button> <p>{open ? <ChatRoom roomId={roomId} /> : "The chat is closed."}</p> </div> );}Open connections: 1Connected to: general
- Connected to general. Open connections: 1
- Disconnected from general. Open connections: 0
- Connected to general. Open connections: 1
ChatRoom appeared, so React set up its Effect. In development, StrictMode then ran the cleanup and the setup once more.
With no array, every letter you type logs a disconnect and a connect. With [], switching rooms logs nothing at all: the heading says travel, but the chat is still connected to general. That's the bug a missing dependency causes, and the reason the linter insists. With [roomId], the Effect runs exactly when it needs to.
How to read the table: each row is a way to write the array, when React runs the Effect, and when it's right.
| How you write it | When the Effect runs | When to use it |
|---|---|---|
useEffect(fn), no array | After every render of the component. | Rarely what you want. Typing in a box reconnects the chat. |
useEffect(fn, []) | Once when the component appears. Its cleanup runs when it goes. | Only right when the Effect reads no props or state. |
useEffect(fn, [roomId]) | When the component appears, and after any render where roomId changed. | The usual case: list every prop and state the Effect reads. |
Cleanup stops what the Effect started
The function an Effect returns is its cleanup. React runs it in two cases: before it runs the Effect again, and when the component leaves the page. That's why the chat never piles up connections: every connect has a matching disconnect.
roomId. When the component goes away, it cleans up for the last time.Each cleanup belongs to one run of the Effect. It's a closure made during that render, so it sees that render's roomId. When you switch to travel, the cleanup that runs is the one from the general render, and it disconnects from general, as it should.
Timers need cleanup too. This page counts how long you've been reading, but its Effect forgets to stop the timer. Watch the number for a few seconds.
import { useEffect, useState } from "react"; export default function App() { const [seconds, setSeconds] = useState(0); useEffect(() => { setInterval(() => { setSeconds((s) => s + 1); }, 1000); }, []); return <p>You've been reading for {seconds} seconds.</p>;}Lines 7 to 9 start a setInterval that adds 1 every second. Line 8 passes an updater function, (s) => s + 1, so each tick adds to the latest value; the lesson on queueing state updates explains why. The number goes up by 2 each second. In development, StrictMode set the Effect up twice, and with nothing to stop the first timer, two timers are running. Here is the fix:
import { useEffect, useState } from "react"; export default function App() { const [seconds, setSeconds] = useState(0); useEffect(() => { const id = setInterval(() => { setSeconds((s) => s + 1); }, 1000); return () => clearInterval(id); }, []); return <p>You've been reading for {seconds} seconds.</p>;}Line 7 keeps the timer's id, and line 10 returns a cleanup that stops it with clearInterval(id). StrictMode still starts a timer, stops it, and starts another, but only one is ever running, so the number goes up by 1 each second.
Why Effects run twice in development
Every live example on this page renders inside <StrictMode>, as a new React project does. In development, when a component appears, StrictMode runs each Effect's setup, then its cleanup, then the setup again, as if the component had been removed and put back at once. Production builds don't do this.
It's a test. People really do leave a page and come back, and components really do disappear and reappear, so the question is whether your cleanup puts things back as they were. If it does, setup, cleanup, setup ends in the same place as one setup. If it doesn't, the bug shows up right away, while you're developing. Here is the chat without its cleanup. Click Open the chat, then close it and open it again.
import { useEffect, useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom() { useEffect(() => { const connection = createConnection("general"); connection.connect(); }, []); return <h3>Welcome to the general room</h3>;} export default function App() { const [open, setOpen] = useState(false); return ( <div> <button onClick={() => setOpen(!open)}>{open ? "Close the chat" : "Open the chat"}</button> {open && <ChatRoom />} </div> );}Opening logs Open connections: 1, then 2: the extra setup connected again, and nothing disconnected. Closing logs nothing. Opening again logs 3 and 4. In production you'd see one connection per opening, but closing would still leave each one open, so they'd pile up just the same, only more slowly. StrictMode made the leak easy to see.
The fix is the cleanup from the first example. A tempting wrong fix is to stop the second run with a ref, so the console looks right:
import { useEffect, useRef, useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom() { const connected = useRef(false); useEffect(() => { // Hiding the second run instead of fixing it (don't do this). if (connected.current) return; connected.current = true; const connection = createConnection("general"); connection.connect(); }, []); return <h3>Welcome to the general room</h3>;} export default function App() { const [open, setOpen] = useState(false); return ( <div> <button onClick={() => setOpen(!open)}>{open ? "Close the chat" : "Open the chat"}</button> {open && <ChatRoom />} </div> );}Lines 9 and 10 skip the Effect if it has run before, so opening logs only Open connections: 1. But close and reopen: 2. Closing never disconnected, and a reopened ChatRoom is a brand-new component with a new ref, so the leak is still there; it's just hidden. Removing <StrictMode> hides it the same way. Fix the cleanup instead.
How to read the table: each row is a common job for an Effect, the setup it does, and the cleanup that undoes it.
| The job | Setup | Cleanup |
|---|---|---|
| Connect to a server | connection.connect() | connection.disconnect() |
| Start a timer | const id = setInterval(…) | clearInterval(id) |
| Listen to a browser event | window.addEventListener("online", handler) | window.removeEventListener("online", handler) |
| Ask a server for data | fetchProduct(id).then(…) | Set an ignore flag, so a late answer is dropped. |
| Send a page visit to analytics | logVisit("/menu") | None. Two visits in development do no harm. |
The last row has no cleanup on purpose. Some Effects are fine to run twice in development, like sending a page visit to an analytics service: the extra visit happens only on your machine. Code that must never run twice, like buying a product, doesn't belong in an Effect: it belongs in the Buy button's event handler.
A support chat with two Effects
A real component often syncs with more than one thing. This support chat stays connected to the room you pick, and shows whether your browser is online. Pick the orders room, type a message, and press Send.
import { useEffect, useState } from "react";import { createConnection } from "./chat.js"; export default function App() { const [roomId, setRoomId] = useState("general"); const [online, setOnline] = useState(window.navigator.onLine); const [text, setText] = useState(""); useEffect(() => { const handleOnline = () => setOnline(true); const handleOffline = () => setOnline(false); window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; }, []); useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); function handleSend(e) { e.preventDefault(); console.log(`Sent to ${roomId}: ${text}`); setText(""); } return ( <form onSubmit={handleSend}> <select value={roomId} onChange={(e) => setRoomId(e.target.value)}> <option value="general">general</option> <option value="orders">orders</option> </select> <span>{online ? " Online " : " Offline "}</span> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="Type a message" /> <button>Send</button> </form> );}The first Effect, lines 9 to 18, subscribes to the browser's online and offline events on window, and its cleanup removes both listeners. It reads no props or state, so its array is []. Line 6 starts online from window.navigator.onLine. The second Effect, lines 20 to 24, is the chat connection, with [roomId]. Each Effect does one job, with its own dependencies.
Sending is not an Effect. It's caused by the submit, so handleSend on lines 26 to 30 logs the message and empties the box. Picking a room logs a disconnect and a connect; typing and sending log neither.
- Connect to the chat room while it's on screen
- Start a countdown when the sale banner appears
- Follow the window's width while the header shows
- Create a map widget in an empty <div>, and destroy it when it goes
- Send the order when the person clicks Buy
- Save the form when it's submitted
- Play a sound when Add to cart is clicked
- Work out the cart's total from its items
- Filter the product list by the search text
Sort each job: does it sync while something is on screen, respond to something the person did, or just calculate?
Common mix-ups
- "An Effect runs while the component renders." No. It runs after React commits the render to the page, so it can use refs and the DOM.
- "
[]means run once, ever." No. It runs each time the component appears, cleans up each time it goes, and gets the extra pair in development. - "I choose what goes in the dependency array." No. Every prop and state value the Effect reads goes in. To run it less often, change the code.
- "The cleanup only runs when the component goes away." No. It also runs before every re-run of the Effect, with the old values.
- "Effects running twice is a bug, so I'll guard them with a ref." No. It's a development check. Write the cleanup, and the second run does no harm.
- "Everything that changes something belongs in an Effect." No. What a person did belongs in an event handler, and calculations belong in rendering.
Practice exercises
Each exercise shows its result under the code, or when you press Edit & run, with the console under it. Change the code, and the result runs again as you type. Make a guess first, then use the hints.
The Effect is written above the log on line 8. Which line does the console print first?
import { useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("B: the Effect");
});
console.log("A: the render");
return <p>Hello, Asha</p>;
}import { useEffect } from "react"; export default function App() { useEffect(() => { console.log("B: the Effect"); }); console.log("A: the render"); return <p>Hello, Asha</p>;}Line 4 only hands the function to React. Line 8 runs during the render, so A: the render prints first. The Effect runs after the commit and prints B: the Effect. In development, each prints twice.
This product page should log each tab you view. Click Reviews: nothing is logged. Fix it. What does clicking Reviews print then?
import { useEffect, useState } from "react"; function logView(tab) { console.log("Viewed:", tab);} export default function App() { const [tab, setTab] = useState("details"); useEffect(() => { logView(tab); }, []); return ( <div> <button onClick={() => setTab("details")}>Details</button> <button onClick={() => setTab("reviews")}>Reviews</button> <p>Showing the {tab} tab.</p> </div> );}import { useEffect, useState } from "react"; function logView(tab) { console.log("Viewed:", tab);} export default function App() { const [tab, setTab] = useState("details"); useEffect(() => { logView(tab); }, [tab]); return ( <div> <button onClick={() => setTab("details")}>Details</button> <button onClick={() => setTab("reviews")}>Reviews</button> <p>Showing the {tab} tab.</p> </div> );}With [tab], React runs the Effect again whenever tab changes, so clicking Reviews logs Viewed: reviews. With [], it only ever logged the first tab.
The flash sale should count down by one each second. What does it say after 2 seconds? Then add the cleanup it's missing.
import { useEffect, useState } from "react"; export default function App() { const [left, setLeft] = useState(10); useEffect(() => { setInterval(() => { setLeft((s) => Math.max(s - 1, 0)); }, 1000); }, []); return <p>Flash sale ends in {left} seconds.</p>;}import { useEffect, useState } from "react"; export default function App() { const [left, setLeft] = useState(10); useEffect(() => { const id = setInterval(() => { setLeft((s) => Math.max(s - 1, 0)); }, 1000); return () => clearInterval(id); }, []); return <p>Flash sale ends in {left} seconds.</p>;}Without a cleanup, StrictMode's extra setup leaves two timers running, so the count drops by 2 each second: 6 after two seconds. With return () => clearInterval(id), the first timer is stopped, and it drops by 1.
Click in the result, outside the box, and press /: the search box gets the focus. How many lines does one press log? Then make it log one.
import { useEffect, useRef } from "react"; export default function App() { const searchRef = useRef(null); useEffect(() => { function handleKey(e) { if (e.key !== "/") return; e.preventDefault(); console.log("Focus the search"); searchRef.current.focus(); } window.addEventListener("keydown", handleKey); }, []); return <input ref={searchRef} placeholder="Press / to search teas" />;}import { useEffect, useRef } from "react"; export default function App() { const searchRef = useRef(null); useEffect(() => { function handleKey(e) { if (e.key !== "/") return; e.preventDefault(); console.log("Focus the search"); searchRef.current.focus(); } window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); }, []); return <input ref={searchRef} placeholder="Press / to search teas" />;}Without a cleanup, two keydown listeners are left on window, so one press logs twice. The cleanup removes the first listener with the same handleKey function, so one press logs once.
ChatRoom should stay connected to the room picked in the menu, and only that room. Write its Effect. After your fix, you pick travel: what is the first line the switch logs?
import { useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom({ roomId }) { // Connect to roomId while ChatRoom is on the page. return <h3>Welcome to the {roomId} room</h3>;} export default function App() { const [roomId, setRoomId] = useState("general"); return ( <div> <select value={roomId} onChange={(e) => setRoomId(e.target.value)}> <option value="general">general</option> <option value="travel">travel</option> </select> <ChatRoom roomId={roomId} /> </div> );}import { useEffect, useState } from "react";import { createConnection } from "./chat.js"; function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h3>Welcome to the {roomId} room</h3>;} export default function App() { const [roomId, setRoomId] = useState("general"); return ( <div> <select value={roomId} onChange={(e) => setRoomId(e.target.value)}> <option value="general">general</option> <option value="travel">travel</option> </select> <ChatRoom roomId={roomId} /> </div> );}The Effect connects to roomId and its cleanup disconnects. When you pick travel, React runs the old cleanup first, Disconnected from general. Open connections: 0, then connects to travel.
Press Run again and click Cake at once. Wait two seconds. What does the page show? Then fix the Effect so a late answer for an old pick is ignored.
import { useEffect, useState } from "react";import { fetchProduct } from "./api.js"; export default function App() { const [id, setId] = useState("tea"); const [product, setProduct] = useState(null); useEffect(() => { fetchProduct(id).then((result) => { setProduct(result); }); }, [id]); return ( <div> <button onClick={() => setId("tea")}>Tea</button> <button onClick={() => setId("cake")}>Cake</button> <p>Picked: {id}. Showing: {product ? product.name : "loading…"}</p> </div> );}import { useEffect, useState } from "react";import { fetchProduct } from "./api.js"; export default function App() { const [id, setId] = useState("tea"); const [product, setProduct] = useState(null); useEffect(() => { let ignore = false; fetchProduct(id).then((result) => { if (!ignore) setProduct(result); }); return () => { ignore = true; }; }, [id]); return ( <div> <button onClick={() => setId("tea")}>Tea</button> <button onClick={() => setId("cake")}>Cake</button> <p>Picked: {id}. Showing: {product ? product.name : "loading…"}</p> </div> );}Picking cake runs the tea Effect's cleanup, which sets that run's ignore to true. When the slow tea answer arrives, its run ignores it, so the page keeps showing Carrot cake. Before the fix, tea arrived last and replaced it: Picked: cake. Showing: Green tea.
Check your understanding
Question 1 of 8In a production build, you type three letters into this box. How many times does it log
Saved draft:in all?Read the code, then predictimport { useEffect, useState } from "react"; export default function App() { const [name, setName] = useState(""); useEffect(() => { console.log("Saved draft:", name); }); return <input value={name} onChange={(e) => setName(e.target.value)} />; }Choose an answer to see the explanation.
Question 2 of 8What does the empty array in
useEffect(fn, [])tell React?Choose an answer to see the explanation.
Question 3 of 8This ChatRoom is showing the general room. You switch it to travel. What does the Effect do?
Read the code, then predictJSXfunction ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h3>Welcome to the {roomId} room</h3>; }Choose an answer to see the explanation.
Question 4 of 8Why does React run an Effect, its cleanup, and the Effect again when a component appears in development?
Choose an answer to see the explanation.
Question 5 of 8In development, your chat shows
Open connections: 2right after it opens. What's the right fix?Choose an answer to see the explanation.
Question 6 of 8What happens when this component appears?
Read the code, then predictJSXimport { useEffect, useState } from "react"; export default function App() { const [visits, setVisits] = useState(0); useEffect(() => { setVisits(visits + 1); }); return <p>Visits: {visits}</p>; }Choose an answer to see the explanation.
Question 7 of 8Where should the code that sends an order to the server go when the person clicks Buy?
Choose an answer to see the explanation.
Question 8 of 8In development, with StrictMode, what does this page show after one second?
Read the code, then predictimport { useEffect, useState } from "react"; export default function App() { const [ticks, setTicks] = useState(0); useEffect(() => { setInterval(() => setTicks((t) => t + 1), 1000); }, []); return <p>Ticks: {ticks}</p>; }Choose an answer to see the explanation.
Key takeaways
- An Effect keeps a component in sync with something outside React: a connection, a timer, a browser event, a widget.
- React runs Effects after it commits a render, so they can use refs and the DOM.
- The dependency array lists what the Effect reads. No array runs after every render,
[]when the component appears,[roomId]also whenroomIdchanges. - The cleanup runs before each re-run and when the component goes. Every setup needs one that undoes it.
- In development, StrictMode runs setup, cleanup, setup when a component appears. Fix the cleanup; don't hide the run.
- Something a person did belongs in an event handler, and a calculation belongs in rendering.
An Effect starts syncing after the commit, and its cleanup stops it.
List what it reads, undo what it does, and let React decide when to run them.
Coming next: You might not need an effect.