React Skill
Comprehensive assistance with react development, generated from official documentation.
When to Use This Skill
This skill should be triggered when:
-
Working with react
-
Asking about react features or APIs
-
Implementing react solutions
-
Debugging react code
-
Learning react best practices
Quick Reference
Common Patterns
Pattern 1: API ReferenceComponentsThe built-in browser component lets you render different kinds of form inputs. Reference Usage Displaying inputs of different types Providing a label for an input Providing an initial value for an input Reading the input values when submitting a form Controlling an input with a state variable Optimizing re-rendering on every keystroke Troubleshooting My text input doesn’t update when I type into it My checkbox doesn’t update when I click on it My input caret jumps to the beginning on every keystroke I’m getting an error: “A component is changing an uncontrolled input to be controlled” Reference To display an input, render the built-in browser component. See more examples below. Props supports all common element props. formAction: A string or function. Overrides the parent for type="submit" and type="image". When a URL is passed to action the form will behave like a standard HTML form. When a function is passed to formAction the function will handle the form submission. See . You can make an input controlled by passing one of these props: checked: A boolean. For a checkbox input or a radio button, controls whether it is selected. value: A string. For a text input, controls its text. (For a radio button, specifies its form data.) When you pass either of them, you must also pass an onChange handler that updates the passed value. These props are only relevant for uncontrolled inputs: defaultChecked: A boolean. Specifies the initial value for type="checkbox" and type="radio" inputs. defaultValue: A string. Specifies the initial value for a text input. These props are relevant both for uncontrolled and controlled inputs: accept: A string. Specifies which filetypes are accepted by a type="file" input. alt: A string. Specifies the alternative image text for a type="image" input. capture: A string. Specifies the media (microphone, video, or camera) captured by a type="file" input. autoComplete: A string. Specifies one of the possible autocomplete behaviors. autoFocus: A boolean. If true, React will focus the element on mount. dirname: A string. Specifies the form field name for the element’s directionality. disabled: A boolean. If true, the input will not be interactive and will appear dimmed. children: does not accept children. form: A string. Specifies the id of the this input belongs to. If omitted, it’s the closest parent form. formAction: A string. Overrides the parent for type="submit" and type="image". formEnctype: A string. Overrides the parent for type="submit" and type="image". formMethod: A string. Overrides the parent for type="submit" and type="image". formNoValidate: A string. Overrides the parent for type="submit" and type="image". formTarget: A string. Overrides the parent for type="submit" and type="image". height: A string. Specifies the image height for type="image". list: A string. Specifies the id of the with the autocomplete options. max: A number. Specifies the maximum value of numerical and datetime inputs. maxLength: A number. Specifies the maximum length of text and other inputs. min: A number. Specifies the minimum value of numerical and datetime inputs. minLength: A number. Specifies the minimum length of text and other inputs. multiple: A boolean. Specifies whether multiple values are allowed for <type="file" and type="email". name: A string. Specifies the name for this input that’s submitted with the form. onChange: An Event handler function. Required for controlled inputs. Fires immediately when the input’s value is changed by the user (for example, it fires on every keystroke). Behaves like the browser input event. onChangeCapture: A version of onChange that fires in the capture phase. onInput: An Event handler function. Fires immediately when the value is changed by the user. For historical reasons, in React it is idiomatic to use onChange instead which works similarly. onInputCapture: A version of onInput that fires in the capture phase. onInvalid: An Event handler function. Fires if an input fails validation on form submit. Unlike the built-in invalid event, the React onInvalid event bubbles. onInvalidCapture: A version of onInvalid that fires in the capture phase. onSelect: An Event handler function. Fires after the selection inside the changes. React extends the onSelect event to also fire for empty selection and on edits (which may affect the selection). onSelectCapture: A version of onSelect that fires in the capture phase. pattern: A string. Specifies the pattern that the value must match. placeholder: A string. Displayed in a dimmed color when the input value is empty. readOnly: A boolean. If true, the input is not editable by the user. required: A boolean. If true, the value must be provided for the form to submit. size: A number. Similar to setting width, but the unit depends on the control. src: A string. Specifies the image source for a type="image" input. step: A positive number or an 'any' string. Specifies the distance between valid values. type: A string. One of the input types. width: A string. Specifies the image width for a type="image" input. Caveats Checkboxes need checked (or defaultChecked), not value (or defaultValue). If a text input receives a string value prop, it will be treated as controlled. If a checkbox or a radio button receives a boolean checked prop, it will be treated as controlled. An input can’t be both controlled and uncontrolled at the same time. An input cannot switch between being controlled or uncontrolled over its lifetime. Every controlled input needs an onChange event handler that synchronously updates its backing value. Usage Displaying inputs of different types To display an input, render an component. By default, it will be a text input. You can pass type="checkbox" for a checkbox, type="radio" for a radio button, or one of the other input types. App.jsApp.jsReloadClearForkexport default function MyForm() { return ( <> Text input: Checkbox: Radio buttons: Option 1 Option 2 Option 3 </> ); } Show more Providing a label for an input Typically, you will place every inside a tag. This tells the browser that this label is associated with that input. When the user clicks the label, the browser will automatically focus the input. It’s also essential for accessibility: a screen reader will announce the label caption when the user focuses the associated input. If you can’t nest into a , associate them by passing the same ID to and . To avoid conflicts between multiple instances of one component, generate such an ID with useId. App.jsApp.jsReloadClearForkimport { useId } from 'react'; export default function Form() { const ageInputId = useId(); return ( <> Your first name: Your age: </> ); } Show more Providing an initial value for an input You can optionally specify the initial value for any input. Pass it as the defaultValue string for text inputs. Checkboxes and radio buttons should specify the initial value with the defaultChecked boolean instead. App.jsApp.jsReloadClearForkexport default function MyForm() { return ( <> Text input: Checkbox: Radio buttons: Option 1 Option 2 Option 3 </> ); } Show more Reading the input values when submitting a form Add a around your inputs with a inside. It will call your event handler. By default, the browser will send the form data to the current URL and refresh the page. You can override that behavior by calling e.preventDefault(). Read the form data with new FormData(e.target). App.jsApp.jsReloadClearForkexport default function MyForm() { function handleSubmit(e) { // Prevent the browser from reloading the page e.preventDefault(); // Read the form data const form = e.target; const formData = new FormData(form); // You can pass formData as a fetch body directly: fetch('/some-api', { method: form.method, body: formData }); // Or you can work with it as a plain object: const formJson = Object.fromEntries(formData.entries()); console.log(formJson); } return ( Text input: Checkbox: Radio buttons: Option 1 Option 2 Option 3 Reset form Submit form ); } Show more NoteGive a name to every , for example . The name you specified will be used as a key in the form data, for example { firstName: "Taylor" }. PitfallBy default, a inside a without a type attribute will submit it. This can be surprising! If you have your own custom Button React component, consider using instead of (with no type). Then, to be explicit, use for buttons that are supposed to submit the form. Controlling an input with a state variable An input like is uncontrolled. Even if you pass an initial value like , your JSX only specifies the initial value. It does not control what the value should be right now. To render a controlled input, pass the value prop to it (or checked for checkboxes and radios). React will force the input to always have the value you passed. Usually, you would do this by declaring a state variable: function Form() { const [firstName, setFirstName] = useState(''); // Declare a state variable... // ... return ( <input value={firstName} // ...force the input's value to match the state variable... onChange={e => setFirstName(e.target.value)} // ... and update the state variable on any edits! /> );} A controlled input makes sense if you needed state anyway—for example, to re-render your UI on every edit: function Form() { const [firstName, setFirstName] = useState(''); return ( <> First name: <input value={firstName} onChange={e => setFirstName(e.target.value)} /> {firstName !== '' && Your name is {firstName}.} ... It’s also useful if you want to offer multiple ways to adjust the input state (for example, by clicking a button): function Form() { // ... const [age, setAge] = useState(''); const ageAsNumber = Number(age); return ( <> Age: <input value={age} onChange={e => setAge(e.target.value)} type="number" /> <button onClick={() => setAge(ageAsNumber + 10)}> Add 10 years The value you pass to controlled components should not be undefined or null. If you need the initial value to be empty (such as with the firstName field below), initialize your state variable to an empty string (''). App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [age, setAge] = useState('20'); const ageAsNumber = Number(age); return ( <> First name: <input value={firstName} onChange={e => setFirstName(e.target.value)} /> Age: <input value={age} onChange={e => setAge(e.target.value)} type="number" /> <button onClick={() => setAge(ageAsNumber + 10)}> Add 10 years {firstName !== '' && Your name is {firstName}. } {ageAsNumber > 0 && Your age is {ageAsNumber}. } </> ); } Show more PitfallIf you pass value without onChange, it will be impossible to type into the input. When you control an input by passing some value to it, you force it to always have the value you passed. So if you pass a state variable as a value but forget to update that state variable synchronously during the onChange event handler, React will revert the input after every keystroke back to the value that you specified. Optimizing re-rendering on every keystroke When you use a controlled input, you set the state on every keystroke. If the component containing your state re-renders a large tree, this can get slow. There’s a few ways you can optimize re-rendering performance. For example, suppose you start with a form that re-renders all page content on every keystroke: function App() { const [firstName, setFirstName] = useState(''); return ( <> <input value={firstName} onChange={e => setFirstName(e.target.value)} /> </> );} Since doesn’t rely on the input state, you can move the input state into its own component: function App() { return ( <> </> );}function SignupForm() { const [firstName, setFirstName] = useState(''); return ( <input value={firstName} onChange={e => setFirstName(e.target.value)} /> );} This significantly improves performance because now only SignupForm re-renders on every keystroke. If there is no way to avoid re-rendering (for example, if PageContent depends on the search input’s value), useDeferredValue lets you keep the controlled input responsive even in the middle of a large re-render. Troubleshooting My text input doesn’t update when I type into it If you render an input with value but no onChange, you will see an error in the console: // 🔴 Bug: controlled text input with no onChange handler ConsoleYou provided a value prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultValue. Otherwise, set either onChange or readOnly. As the error message suggests, if you only wanted to specify the initial value, pass defaultValue instead: // ✅ Good: uncontrolled input with an initial value If you want to control this input with a state variable, specify an onChange handler: // ✅ Good: controlled input with onChange<input value={something} onChange={e => setSomething(e.target.value)} /> If the value is intentionally read-only, add a readOnly prop to suppress the error: // ✅ Good: readonly controlled input without on change My checkbox doesn’t update when I click on it If you render a checkbox with checked but no onChange, you will see an error in the console: // 🔴 Bug: controlled checkbox with no onChange handler ConsoleYou provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly. As the error message suggests, if you only wanted to specify the initial value, pass defaultChecked instead: // ✅ Good: uncontrolled checkbox with an initial value If you want to control this checkbox with a state variable, specify an onChange handler: // ✅ Good: controlled checkbox with onChange<input type="checkbox" checked={something} onChange={e => setSomething(e.target.checked)} /> PitfallYou need to read e.target.checked rather than e.target.value for checkboxes. If the checkbox is intentionally read-only, add a readOnly prop to suppress the error: // ✅ Good: readonly controlled input without on change My input caret jumps to the beginning on every keystroke If you control an input, you must update its state variable to the input’s value from the DOM during onChange. You can’t update it to something other than e.target.value (or e.target.checked for checkboxes): function handleChange(e) { // 🔴 Bug: updating an input to something other than e.target.value setFirstName(e.target.value.toUpperCase());} You also can’t update it asynchronously: function handleChange(e) { // 🔴 Bug: updating an input asynchronously setTimeout(() => { setFirstName(e.target.value); }, 100);} To fix your code, update it synchronously to e.target.value: function handleChange(e) { // ✅ Updating a controlled input to e.target.value synchronously setFirstName(e.target.value);} If this doesn’t fix the problem, it’s possible that the input gets removed and re-added from the DOM on every keystroke. This can happen if you’re accidentally resetting state on every re-render, for example if the input or one of its parents always receives a different key attribute, or if you nest component function definitions (which is not supported and causes the “inner” component to always be considered a different tree). I’m getting an error: “A component is changing an uncontrolled input to be controlled” If you provide a value to the component, it must remain a string throughout its lifetime. You cannot pass value={undefined} first and later pass value="some string" because React won’t know whether you want the component to be uncontrolled or controlled. A controlled component should always receive a string value, not null or undefined. If your value is coming from an API or a state variable, it might be initialized to null or undefined. In that case, either set it to an empty string ('') initially, or pass value={someValue ?? ''} to ensure value is a string. Similarly, if you pass checked to a checkbox, ensure it’s always a boolean.PreviousNext
<input>
Pattern 2: API ReferenceComponents lets you hide and restore the UI and internal state of its children. Reference Usage Restoring the state of hidden components Restoring the DOM of hidden components Pre-rendering content that’s likely to become visible Speeding up interactions during page load Troubleshooting My hidden components have unwanted side effects My hidden components have Effects that aren’t running Reference You can use Activity to hide part of your application: <Activity mode={isShowingSidebar ? "visible" : "hidden"}> When an Activity boundary is hidden, React will visually hide its children using the display: "none" CSS property. It will also destroy their Effects, cleaning up any active subscriptions. While hidden, children still re-render in response to new props, albeit at a lower priority than the rest of the content. When the boundary becomes visible again, React will reveal the children with their previous state restored, and re-create their Effects. In this way, Activity can be thought of as a mechanism for rendering “background activity”. Rather than completely discarding content that’s likely to become visible again, you can use Activity to maintain and restore that content’s UI and internal state, while ensuring that your hidden content has no unwanted side effects. See more examples below. Props children: The UI you intend to show and hide. mode: A string value of either 'visible' or 'hidden'. If omitted, defaults to 'visible'. Caveats If an Activity is rendered inside of a ViewTransition, and it becomes visible as a result of an update caused by startTransition, it will activate the ViewTransition’s enter animation. If it becomes hidden, it will activate its exit animation. An Activity that just renders text will not render anything rather than rendering hidden text, because there’s no corresponding DOM element to apply visibility changes to. For example, will not produce any output in the DOM for const ComponentThatJustReturnsText = () => "Hello, World!". Usage Restoring the state of hidden components In React, when you want to conditionally show or hide a component, you typically mount or unmount it based on that condition: {isShowingSidebar && ( )} But unmounting a component destroys its internal state, which is not always what you want. When you hide a component using an Activity boundary instead, React will “save” its state for later: <Activity mode={isShowingSidebar ? "visible" : "hidden"}> This makes it possible to hide and then later restore components in the state they were previously in. The following example has a sidebar with an expandable section. You can press “Overview” to reveal the three subitems below it. The main app area also has a button that hides and shows the sidebar. Try expanding the Overview section, and then toggling the sidebar closed then open: App.jsSidebar.jsApp.jsReloadClearForkimport { useState } from 'react'; import Sidebar from './Sidebar.js'; export default function App() { const [isShowingSidebar, setIsShowingSidebar] = useState(true); return ( <> {isShowingSidebar && ( )} <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}> Toggle sidebar Main content </> ); } Show more The Overview section always starts out collapsed. Because we unmount the sidebar when isShowingSidebar flips to false, all its internal state is lost. This is a perfect use case for Activity. We can preserve the internal state of our sidebar, even when visually hiding it. Let’s replace the conditional rendering of our sidebar with an Activity boundary: // Before{isShowingSidebar && ( )}// After<Activity mode={isShowingSidebar ? 'visible' : 'hidden'}> and check out the new behavior: App.jsSidebar.jsApp.jsReloadClearForkimport { Activity, useState } from 'react'; import Sidebar from './Sidebar.js'; export default function App() { const [isShowingSidebar, setIsShowingSidebar] = useState(true); return ( <> <Activity mode={isShowingSidebar ? 'visible' : 'hidden'}> <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}> Toggle sidebar Main content </> ); } Show more Our sidebar’s internal state is now restored, without any changes to its implementation. Restoring the DOM of hidden components Since Activity boundaries hide their children using display: none, their children’s DOM is also preserved when hidden. This makes them great for maintaining ephemeral state in parts of the UI that the user is likely to interact with again. In this example, the Contact tab has a where the user can enter a message. If you enter some text, change to the Home tab, then change back to the Contact tab, the draft message is lost: App.jsTabButton.jsHome.jsContact.jsContact.jsReloadClearForkexport default function Contact() { return ( Send me a message! You can find me online here: admin@mysite.com +123456789 ); } This is because we’re fully unmounting Contact in App. When the Contact tab unmounts, the element’s internal DOM state is lost. If we switch to using an Activity boundary to show and hide the active tab, we can preserve the state of each tab’s DOM. Try entering text and switching tabs again, and you’ll see the draft message is no longer reset: App.jsTabButton.jsHome.jsContact.jsApp.jsReloadClearForkimport { Activity, useState } from 'react'; import TabButton from './TabButton.js'; import Home from './Home.js'; import Contact from './Contact.js'; export default function App() { const [activeTab, setActiveTab] = useState('contact'); return ( <> <TabButton isActive={activeTab === 'home'} onClick={() => setActiveTab('home')} > Home <TabButton isActive={activeTab === 'contact'} onClick={() => setActiveTab('contact')} > Contact <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}> <Activity mode={activeTab === 'contact' ? 'visible' : 'hidden'}> </> ); } Show more Again, the Activity boundary let us preserve the Contact tab’s internal state without changing its implementation. Pre-rendering content that’s likely to become visible So far, we’ve seen how Activity can hide some content that the user has interacted with, without discarding that content’s ephemeral state. But Activity boundaries can also be used to prepare content that the user has yet to see for the first time: When an Activity boundary is hidden during its initial render, its children won’t be visible on the page — but they will still be rendered, albeit at a lower priority than the visible content, and without mounting their Effects. This pre-rendering allows the children to load any code or data they need ahead of time, so that later, when the Activity boundary becomes visible, the children can appear faster with reduced loading times. Let’s look at an example. In this demo, the Posts tab loads some data. If you press it, you’ll see a Suspense fallback displayed while the data is being fetched: App.jsHome.jsPosts.jsApp.jsReloadClearForkimport { useState, Suspense } from 'react'; import TabButton from './TabButton.js'; import Home from './Home.js'; import Posts from './Posts.js'; export default function App() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton isActive={activeTab === 'home'} onClick={() => setActiveTab('home')} > Home <TabButton isActive={activeTab === 'posts'} onClick={() => setActiveTab('posts')} > Posts <Suspense fallback={🌀 Loading...}> {activeTab === 'home' && } {activeTab === 'posts' && } </> ); } Show more This is because App doesn’t mount Posts until its tab is active. If we update App to use an Activity boundary to show and hide the active tab, Posts will be pre-rendered when the app first loads, allowing it to fetch its data before it becomes visible. Try clicking the Posts tab now: App.jsHome.jsPosts.jsApp.jsReloadClearForkimport { Activity, useState, Suspense } from 'react'; import TabButton from './TabButton.js'; import Home from './Home.js'; import Posts from './Posts.js'; export default function App() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton isActive={activeTab === 'home'} onClick={() => setActiveTab('home')} > Home <TabButton isActive={activeTab === 'posts'} onClick={() => setActiveTab('posts')} > Posts <Suspense fallback={🌀 Loading...}> <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}> <Activity mode={activeTab === 'posts' ? 'visible' : 'hidden'}> </> ); } Show more Posts was able to prepare itself for a faster render, thanks to the hidden Activity boundary. Pre-rendering components with hidden Activity boundaries is a powerful way to reduce loading times for parts of the UI that the user is likely to interact with next. NoteOnly Suspense-enabled data sources will be fetched during pre-rendering. They include: Data fetching with Suspense-enabled frameworks like Relay and Next.js Lazy-loading component code with lazy Reading the value of a cached Promise with use Activity does not detect data that is fetched inside an Effect.The exact way you would load data in the Posts component above depends on your framework. If you use a Suspense-enabled framework, you’ll find the details in its data fetching documentation.Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. Speeding up interactions during page load React includes an under-the-hood performance optimization called Selective Hydration. It works by hydrating your app’s initial HTML in chunks, enabling some components to become interactive even if other components on the page haven’t loaded their code or data yet. Suspense boundaries participate in Selective Hydration, because they naturally divide your component tree into units that are independent from one another: function Page() { return ( <> </> )} Here, MessageComposer can be fully hydrated during the initial render of the page, even before Chats is mounted and starts to fetch its data. So by breaking up your component tree into discrete units, Suspense allows React to hydrate your app’s server-rendered HTML in chunks, enabling parts of your app to become interactive as fast as possible. But what about pages that don’t use Suspense? Take this tabs example: function Page() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton onClick={() => setActiveTab('home')}> Home <TabButton onClick={() => setActiveTab('video')}> Video {activeTab === 'home' && ( )} {activeTab === 'video' && ( )} </> )} Here, React must hydrate the entire page all at once. If Home or Video are slower to render, they could make the tab buttons feel unresponsive during hydration. Adding Suspense around the active tab would solve this: function Page() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton onClick={() => setActiveTab('home')}> Home <TabButton onClick={() => setActiveTab('video')}> Video <Suspense fallback={}> {activeTab === 'home' && ( )} {activeTab === 'video' && ( )} </> )} …but it would also change the UI, since the Placeholder fallback would be displayed on the initial render. Instead, we can use Activity. Since Activity boundaries show and hide their children, they already naturally divide the component tree into independent units. And just like Suspense, this feature allows them to participate in Selective Hydration. Let’s update our example to use Activity boundaries around the active tab: function Page() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton onClick={() => setActiveTab('home')}> Home <TabButton onClick={() => setActiveTab('video')}> Video <Activity mode={activeTab === "home" ? "visible" : "hidden"}> <Activity mode={activeTab === "video" ? "visible" : "hidden"}> </> )} Now our initial server-rendered HTML looks the same as it did in the original version, but thanks to Activity, React can hydrate the tab buttons first, before it even mounts Home or Video. Thus, in addition to hiding and showing content, Activity boundaries help improve your app’s performance during hydration by letting React know which parts of your page can become interactive in isolation. And even if your page doesn’t ever hide part of its content, you can still add always-visible Activity boundaries to improve hydration performance: function Page() { return ( <> </> );} Troubleshooting My hidden components have unwanted side effects An Activity boundary hides its content by setting display: none on its children and cleaning up any of their Effects. So, most well-behaved React components that properly clean up their side effects will already be robust to being hidden by Activity. But there are some situations where a hidden component behaves differently than an unmounted one. Most notably, since a hidden component’s DOM is not destroyed, any side effects from that DOM will persist, even after the component is hidden. As an example, consider a tag. Typically it doesn’t require any cleanup, because even if you’re playing a video, unmounting the tag stops the video and audio from playing in the browser. Try playing the video and then pressing Home in this demo: App.jsHome.jsVideo.jsApp.jsReloadClearForkimport { useState } from 'react'; import TabButton from './TabButton.js'; import Home from './Home.js'; import Video from './Video.js'; export default function App() { const [activeTab, setActiveTab] = useState('video'); return ( <> <TabButton isActive={activeTab === 'home'} onClick={() => setActiveTab('home')} > Home <TabButton isActive={activeTab === 'video'} onClick={() => setActiveTab('video')} > Video {activeTab === 'home' && } {activeTab === 'video' && } </> ); } Show more The video stops playing as expected. Now, let’s say we wanted to preserve the timecode where the user last watched, so that when they tab back to the video, it doesn’t start over from the beginning again. This is a great use case for Activity! Let’s update App to hide the inactive tab with a hidden Activity boundary instead of unmounting it, and see how the demo behaves this time: App.jsHome.jsVideo.jsApp.jsReloadClearForkimport { Activity, useState } from 'react'; import TabButton from './TabButton.js'; import Home from './Home.js'; import Video from './Video.js'; export default function App() { const [activeTab, setActiveTab] = useState('video'); return ( <> <TabButton isActive={activeTab === 'home'} onClick={() => setActiveTab('home')} > Home <TabButton isActive={activeTab === 'video'} onClick={() => setActiveTab('video')} > Video <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}> <Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}> </> ); } Show more Whoops! The video and audio continue to play even after it’s been hidden, because the tab’s element is still in the DOM. To fix this, we can add an Effect with a cleanup function that pauses the video: export default function VideoTab() { const ref = useRef(); useLayoutEffect(() => { const videoRef = ref.current; return () => { videoRef.pause() } }, []); return ( );} We call useLayoutEffect instead of useEffect because conceptually the clean-up code is tied to the component’s UI being visually hidden. If we used a regular effect, the code could be delayed by (say) a re-suspending Suspense boundary or a View Transition. Let’s see the new behavior. Try playing the video, switching to the Home tab, then back to the Video tab: App.jsHome.jsVideo.jsApp.jsReloadClearForkimport { Activity, useState } from 'react'; import TabButton from './TabButton.js'; import Home from './Home.js'; import Video from './Video.js'; export default function App() { const [activeTab, setActiveTab] = useState('video'); return ( <> <TabButton isActive={activeTab === 'home'} onClick={() => setActiveTab('home')} > Home <TabButton isActive={activeTab === 'video'} onClick={() => setActiveTab('video')} > Video <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}> <Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}> </> ); } Show more It works great! Our cleanup function ensures that the video stops playing if it’s ever hidden by an Activity boundary, and even better, because the tag is never destroyed, the timecode is preserved, and the video itself doesn’t need to be initialized or downloaded again when the user switches back to keep watching it. This is a great example of using Activity to preserve ephemeral DOM state for parts of the UI that become hidden, but the user is likely to interact with again soon. Our example illustrates that for certain tags like , unmounting and hiding have different behavior. If a component renders DOM that has a side effect, and you want to prevent that side effect when an Activity boundary hides it, add an Effect with a return function to clean it up. The most common cases of this will be from the following tags: Typically, though, most of your React components should already be robust to being hidden by an Activity boundary. And conceptually, you should think of “hidden” Activities as being unmounted. To eagerly discover other Effects that don’t have proper cleanup, which is important not only for Activity boundaries but for many other behaviors in React, we recommend using . My hidden components have Effects that aren’t running When an is “hidden”, all its children’s Effects are cleaned up. Conceptually, the children are unmounted, but React saves their state for later. This is a feature of Activity because it means subscriptions won’t be active for hidden parts of the UI, reducing the amount of work needed for hidden content. If you’re relying on an Effect mounting to clean up a component’s side effects, refactor the Effect to do the work in the returned cleanup function instead. To eagerly find problematic Effects, we recommend adding which will eagerly perform Activity unmounts and mounts to catch any unexpected side-effects.PreviousNext
<Activity>
Pattern 3: Posts was able to prepare itself for a faster render, thanks to the hidden Activity boundary. Pre-rendering components with hidden Activity boundaries is a powerful way to reduce loading times for parts of the UI that the user is likely to interact with next. NoteOnly Suspense-enabled data sources will be fetched during pre-rendering. They include: Data fetching with Suspense-enabled frameworks like Relay and Next.js Lazy-loading component code with lazy Reading the value of a cached Promise with use Activity does not detect data that is fetched inside an Effect.The exact way you would load data in the Posts component above depends on your framework. If you use a Suspense-enabled framework, you’ll find the details in its data fetching documentation.Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. Speeding up interactions during page load React includes an under-the-hood performance optimization called Selective Hydration. It works by hydrating your app’s initial HTML in chunks, enabling some components to become interactive even if other components on the page haven’t loaded their code or data yet. Suspense boundaries participate in Selective Hydration, because they naturally divide your component tree into units that are independent from one another: function Page() { return ( <> </> )} Here, MessageComposer can be fully hydrated during the initial render of the page, even before Chats is mounted and starts to fetch its data. So by breaking up your component tree into discrete units, Suspense allows React to hydrate your app’s server-rendered HTML in chunks, enabling parts of your app to become interactive as fast as possible. But what about pages that don’t use Suspense? Take this tabs example: function Page() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton onClick={() => setActiveTab('home')}> Home <TabButton onClick={() => setActiveTab('video')}> Video {activeTab === 'home' && ( )} {activeTab === 'video' && ( )} </> )} Here, React must hydrate the entire page all at once. If Home or Video are slower to render, they could make the tab buttons feel unresponsive during hydration. Adding Suspense around the active tab would solve this: function Page() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton onClick={() => setActiveTab('home')}> Home <TabButton onClick={() => setActiveTab('video')}> Video <Suspense fallback={}> {activeTab === 'home' && ( )} {activeTab === 'video' && ( )} </> )} …but it would also change the UI, since the Placeholder fallback would be displayed on the initial render. Instead, we can use Activity. Since Activity boundaries show and hide their children, they already naturally divide the component tree into independent units. And just like Suspense, this feature allows them to participate in Selective Hydration. Let’s update our example to use Activity boundaries around the active tab: function Page() { const [activeTab, setActiveTab] = useState('home'); return ( <> <TabButton onClick={() => setActiveTab('home')}> Home <TabButton onClick={() => setActiveTab('video')}> Video <Activity mode={activeTab === "home" ? "visible" : "hidden"}> <Activity mode={activeTab === "video" ? "visible" : "hidden"}> </> )} Now our initial server-rendered HTML looks the same as it did in the original version, but thanks to Activity, React can hydrate the tab buttons first, before it even mounts Home or Video. Thus, in addition to hiding and showing content, Activity boundaries help improve your app’s performance during hydration by letting React know which parts of your page can become interactive in isolation. And even if your page doesn’t ever hide part of its content, you can still add always-visible Activity boundaries to improve hydration performance: function Page() { return ( <> </> );} Troubleshooting My hidden components have unwanted side effects An Activity boundary hides its content by setting display: none on its children and cleaning up any of their Effects. So, most well-behaved React components that properly clean up their side effects will already be robust to being hidden by Activity. But there are some situations where a hidden component behaves differently than an unmounted one. Most notably, since a hidden component’s DOM is not destroyed, any side effects from that DOM will persist, even after the component is hidden. As an example, consider a tag. Typically it doesn’t require any cleanup, because even if you’re playing a video, unmounting the tag stops the video and audio from playing in the browser. Try playing the video and then pressing Home in this demo:
Posts
Pattern 4: Learn ReactManaging StateChoosing the State StructureStructuring state well can make a difference between a component that is pleasant to modify and debug, and one that is a constant source of bugs. Here are some tips you should consider when structuring state. You will learn When to use a single vs multiple state variables What to avoid when organizing state How to fix common issues with the state structure Principles for structuring state When you write a component that holds some state, you’ll have to make choices about how many state variables to use and what the shape of their data should be. While it’s possible to write correct programs even with a suboptimal state structure, there are a few principles that can guide you to make better choices: Group related state. If you always update two or more state variables at the same time, consider merging them into a single state variable. Avoid contradictions in state. When the state is structured in a way that several pieces of state may contradict and “disagree” with each other, you leave room for mistakes. Try to avoid this. Avoid redundant state. If you can calculate some information from the component’s props or its existing state variables during rendering, you should not put that information into that component’s state. Avoid duplication in state. When the same data is duplicated between multiple state variables, or within nested objects, it is difficult to keep them in sync. Reduce duplication when you can. Avoid deeply nested state. Deeply hierarchical state is not very convenient to update. When possible, prefer to structure state in a flat way. The goal behind these principles is to make state easy to update without introducing mistakes. Removing redundant and duplicate data from state helps ensure that all its pieces stay in sync. This is similar to how a database engineer might want to “normalize” the database structure to reduce the chance of bugs. To paraphrase Albert Einstein, “Make your state as simple as it can be—but no simpler.” Now let’s see how these principles apply in action. Group related state You might sometimes be unsure between using a single or multiple state variables. Should you do this? const [x, setX] = useState(0);const [y, setY] = useState(0); Or this? const [position, setPosition] = useState({ x: 0, y: 0 }); Technically, you can use either of these approaches. But if some two state variables always change together, it might be a good idea to unify them into a single state variable. Then you won’t forget to always keep them in sync, like in this example where moving the cursor updates both coordinates of the red dot: App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function MovingDot() { const [position, setPosition] = useState({ x: 0, y: 0 }); return ( <div onPointerMove={e => { setPosition({ x: e.clientX, y: e.clientY }); }} style={{ position: 'relative', width: '100vw', height: '100vh', }}> <div style={{ position: 'absolute', backgroundColor: 'red', borderRadius: '50%', transform: translate(${position.x}px, ${position.y}px) , left: -10, top: -10, width: 20, height: 20, }} /> ) } Show more Another case where you’ll group data into an object or an array is when you don’t know how many pieces of state you’ll need. For example, it’s helpful when you have a form where the user can add custom fields. PitfallIf your state variable is an object, remember that you can’t update only one field in it without explicitly copying the other fields. For example, you can’t do setPosition({ x: 100 }) in the above example because it would not have the y property at all! Instead, if you wanted to set x alone, you would either do setPosition({ ...position, x: 100 }), or split them into two state variables and do setX(100). Avoid contradictions in state Here is a hotel feedback form with isSending and isSent state variables: App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); const [isSent, setIsSent] = useState(false); async function handleSubmit(e) { e.preventDefault(); setIsSending(true); await sendMessage(text); setIsSending(false); setIsSent(true); } if (isSent) { return Thanks for feedback! } return ( How was your stay at The Prancing Pony? <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> Send {isSending && Sending...} ); } // Pretend to send a message. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); } Show more While this code works, it leaves the door open for “impossible” states. For example, if you forget to call setIsSent and setIsSending together, you may end up in a situation where both isSending and isSent are true at the same time. The more complex your component is, the harder it is to understand what happened. Since isSending and isSent should never be true at the same time, it is better to replace them with one status state variable that may take one of three valid states: 'typing' (initial), 'sending', and 'sent': App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [status, setStatus] = useState('typing'); async function handleSubmit(e) { e.preventDefault(); setStatus('sending'); await sendMessage(text); setStatus('sent'); } const isSending = status === 'sending'; const isSent = status === 'sent'; if (isSent) { return Thanks for feedback! } return ( How was your stay at The Prancing Pony? <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> Send {isSending && Sending...} ); } // Pretend to send a message. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); } Show more You can still declare some constants for readability: const isSending = status === 'sending';const isSent = status === 'sent'; But they’re not state variables, so you don’t need to worry about them getting out of sync with each other. Avoid redundant state If you can calculate some information from the component’s props or its existing state variables during rendering, you should not put that information into that component’s state. For example, take this form. It works, but can you find any redundant state in it? App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> Let’s check you in First name:{' '} Last name:{' '} Your ticket will be issued to: {fullName} </> ); } Show more This form has three state variables: firstName, lastName, and fullName. However, fullName is redundant. You can always calculate fullName from firstName and lastName during render, so remove it from state. This is how you can do it: App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> Let’s check you in First name:{' '} Last name:{' '} Your ticket will be issued to: {fullName} </> ); } Show more Here, fullName is not a state variable. Instead, it’s calculated during render: const fullName = firstName + ' ' + lastName; As a result, the change handlers don’t need to do anything special to update it. When you call setFirstName or setLastName, you trigger a re-render, and then the next fullName will be calculated from the fresh data. Deep DiveDon’t mirror props in state Show DetailsA common example of redundant state is code like this:function Message({ messageColor }) { const [color, setColor] = useState(messageColor);Here, a color state variable is initialized to the messageColor prop. The problem is that if the parent component passes a different value of messageColor later (for example, 'red' instead of 'blue'), the color state variable would not be updated! The state is only initialized during the first render.This is why “mirroring” some prop in a state variable can lead to confusion. Instead, use the messageColor prop directly in your code. If you want to give it a shorter name, use a constant:function Message({ messageColor }) { const color = messageColor;This way it won’t get out of sync with the prop passed from the parent component.”Mirroring” props into state only makes sense when you want to ignore all updates for a specific prop. By convention, start the prop name with initial or default to clarify that its new values are ignored:function Message({ initialColor }) { // The color state variable holds the first value of initialColor . // Further changes to the initialColor prop are ignored. const [color, setColor] = useState(initialColor); Avoid duplication in state This menu list component lets you choose a single travel snack out of several: App.jsApp.jsReloadClearForkimport { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); return ( <> What's your travel snack? {items.map(item => ( {item.title} {' '} <button onClick={() => { setSelectedItem(item); }}>Choose ))} You picked {selectedItem.title}. </> ); } Show more Currently, it stores the selected item as an object in the selectedItem state variable. However, this is not great: the contents of the selectedItem is the same object as one of the items inside the items list. This means that the information about the item itself is duplicated in two places. Why is this a problem? Let’s make each item editable: App.jsApp.jsReloadClearForkimport { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> What's your travel snack? {items.map((item, index) => ( <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedItem(item); }}>Choose ))} You picked {selectedItem.title}. </> ); } Show more Notice how if you first click “Choose” on an item and then edit it, the input updates but the label at the bottom does not reflect the edits. This is because you have duplicated state, and you forgot to update selectedItem. Although you could update selectedItem too, an easier fix is to remove duplication. In this example, instead of a selectedItem object (which creates a duplication with objects inside items), you hold the selectedId in state, and then get the selectedItem by searching the items array for an item with that ID: App.jsApp.jsReloadClearForkimport { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedId, setSelectedId] = useState(0); const selectedItem = items.find(item => item.id === selectedId ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> What's your travel snack? {items.map((item, index) => ( <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedId(item.id); }}>Choose ))} You picked {selectedItem.title}. </> ); } Show more The state used to be duplicated like this: items = [{ id: 0, title: 'pretzels'}, ...] selectedItem = {id: 0, title: 'pretzels'} But after the change it’s like this: items = [{ id: 0, title: 'pretzels'}, ...] selectedId = 0 The duplication is gone, and you only keep the essential state! Now if you edit the selected item, the message below will update immediately. This is because setItems triggers a re-render, and items.find(...) would find the item with the updated title. You didn’t need to hold the selected item in state, because only the selected ID is essential. The rest could be calculated during render. Avoid deeply nested state Imagine a travel plan consisting of planets, continents, and countries. You might be tempted to structure its state using nested objects and arrays, like in this example: App.jsplaces.jsplaces.jsReloadClearForkexport const initialTravelPlan = { id: 0, title: '(Root)', childPlaces: [{ id: 1, title: 'Earth', childPlaces: [{ id: 2, title: 'Africa', childPlaces: [{ id: 3, title: 'Botswana', childPlaces: [] }, { id: 4, title: 'Egypt', childPlaces: [] }, { id: 5, title: 'Kenya', childPlaces: [] }, { id: 6, title: 'Madagascar', childPlaces: [] }, { id: 7, title: 'Morocco', childPlaces: [] }, { id: 8, title: 'Nigeria', childPlaces: [] }, { id: 9, title: 'South Africa', childPlaces: [] }] }, { id: 10, title: 'Americas', childPlaces: [{ id: 11, title: 'Argentina', childPlaces: [] }, { id: 12, title: 'Brazil', childPlaces: [] }, { id: 13, title: 'Barbados', childPlaces: [] }, { id: 14, title: 'Canada', childPlaces: [] }, { id: 15, title: 'Jamaica', childPlaces: [] }, { id: 16, title: 'Mexico', childPlaces: [] }, { id: 17, title: 'Trinidad and Tobago', childPlaces: [] }, { id: 18, title: 'Venezuela', childPlaces: [] }] }, { id: 19, title: 'Asia', childPlaces: [{ id: 20, title: 'China', childPlaces: [] }, { id: 21, title: 'India', childPlaces: [] }, { id: 22, title: 'Singapore', childPlaces: [] }, { id: 23, title: 'South Korea', childPlaces: [] }, { id: 24, title: 'Thailand', childPlaces: [] }, { id: 25, title: 'Vietnam', childPlaces: [] }] }, { id: 26, title: 'Europe', childPlaces: [{ id: 27, title: 'Croatia', childPlaces: [], }, { id: 28, title: 'France', childPlaces: [], }, { id: 29, title: 'Germany', childPlaces: [], }, { id: 30, title: 'Italy', childPlaces: [], }, { id: 31, title: 'Portugal', childPlaces: [], }, { id: 32, title: 'Spain', childPlaces: [], }, { id: 33, title: 'Turkey', childPlaces: [], }] }, { id: 34, title: 'Oceania', childPlaces: [{ id: 35, title: 'Australia', childPlaces: [], }, { id: 36, title: 'Bora Bora (French Polynesia)', childPlaces: [], }, { id: 37, title: 'Easter Island (Chile)', childPlaces: [], }, { id: 38, title: 'Fiji', childPlaces: [], }, { id: 39, title: 'Hawaii (the USA)', childPlaces: [], }, { id: 40, title: 'New Zealand', childPlaces: [], }, { id: 41, title: 'Vanuatu', childPlaces: [], }] }] }, { id: 42, title: 'Moon', childPlaces: [{ id: 43, title: 'Rheita', childPlaces: [] }, { id: 44, title: 'Piccolomini', childPlaces: [] }, { id: 45, title: 'Tycho', childPlaces: [] }] }, { id: 46, title: 'Mars', childPlaces: [{ id: 47, title: 'Corn Town', childPlaces: [] }, { id: 48, title: 'Green Hill', childPlaces: [] }] }] }; Show more Now let’s say you want to add a button to delete a place you’ve already visited. How would you go about it? Updating nested state involves making copies of objects all the way up from the part that changed. Deleting a deeply nested place would involve copying its entire parent place chain. Such code can be very verbose. If the state is too nested to update easily, consider making it “flat”. Here is one way you can restructure this data. Instead of a tree-like structure where each place has an array of its child places, you can have each place hold an array of its child place IDs. Then store a mapping from each place ID to the corresponding place. This data restructuring might remind you of seeing a database table: App.jsplaces.jsplaces.jsReloadClearForkexport const initialTravelPlan = { 0: { id: 0, title: '(Root)', childIds: [1, 42, 46], }, 1: { id: 1, title: 'Earth', childIds: [2, 10, 19, 26, 34] }, 2: { id: 2, title: 'Africa', childIds: [3, 4, 5, 6 , 7, 8, 9] }, 3: { id: 3, title: 'Botswana', childIds: [] }, 4: { id: 4, title: 'Egypt', childIds: [] }, 5: { id: 5, title: 'Kenya', childIds: [] }, 6: { id: 6, title: 'Madagascar', childIds: [] }, 7: { id: 7, title: 'Morocco', childIds: [] }, 8: { id: 8, title: 'Nigeria', childIds: [] }, 9: { id: 9, title: 'South Africa', childIds: [] }, 10: { id: 10, title: 'Americas', childIds: [11, 12, 13, 14, 15, 16, 17, 18], }, 11: { id: 11, title: 'Argentina', childIds: [] }, 12: { id: 12, title: 'Brazil', childIds: [] }, 13: { id: 13, title: 'Barbados', childIds: [] }, 14: { id: 14, title: 'Canada', childIds: [] }, 15: { id: 15, title: 'Jamaica', childIds: [] }, 16: { id: 16, title: 'Mexico', childIds: [] }, 17: { id: 17, title: 'Trinidad and Tobago', childIds: [] }, 18: { id: 18, title: 'Venezuela', childIds: [] }, 19: { id: 19, title: 'Asia', childIds: [20, 21, 22, 23, 24, 25], }, 20: { id: 20, title: 'China', childIds: [] }, 21: { id: 21, title: 'India', childIds: [] }, 22: { id: 22, title: 'Singapore', childIds: [] }, 23: { id: 23, title: 'South Korea', childIds: [] }, 24: { id: 24, title: 'Thailand', childIds: [] }, 25: { id: 25, title: 'Vietnam', childIds: [] }, 26: { id: 26, title: 'Europe', childIds: [27, 28, 29, 30, 31, 32, 33], }, 27: { id: 27, title: 'Croatia', childIds: [] }, 28: { id: 28, title: 'France', childIds: [] }, 29: { id: 29, title: 'Germany', childIds: [] }, 30: { id: 30, title: 'Italy', childIds: [] }, 31: { id: 31, title: 'Portugal', childIds: [] }, 32: { id: 32, title: 'Spain', childIds: [] }, 33: { id: 33, title: 'Turkey', childIds: [] }, 34: { id: 34, title: 'Oceania', childIds: [35, 36, 37, 38, 39, 40, 41], }, 35: { id: 35, title: 'Australia', childIds: [] }, 36: { id: 36, title: 'Bora Bora (French Polynesia)', childIds: [] }, 37: { id: 37, title: 'Easter Island (Chile)', childIds: [] }, 38: { id: 38, title: 'Fiji', childIds: [] }, 39: { id: 40, title: 'Hawaii (the USA)', childIds: [] }, 40: { id: 40, title: 'New Zealand', childIds: [] }, 41: { id: 41, title: 'Vanuatu', childIds: [] }, 42: { id: 42, title: 'Moon', childIds: [43, 44, 45] }, 43: { id: 43, title: 'Rheita', childIds: [] }, 44: { id: 44, title: 'Piccolomini', childIds: [] }, 45: { id: 45, title: 'Tycho', childIds: [] }, 46: { id: 46, title: 'Mars', childIds: [47, 48] }, 47: { id: 47, title: 'Corn Town', childIds: [] }, 48: { id: 48, title: 'Green Hill', childIds: [] } }; Show more Now that the state is “flat” (also known as “normalized”), updating nested items becomes easier. In order to remove a place now, you only need to update two levels of state: The updated version of its parent place should exclude the removed ID from its childIds array. The updated version of the root “table” object should include the updated version of the parent place. Here is an example of how you could go about it: App.jsplaces.jsApp.jsReloadClearForkimport { useState } from 'react'; import { initialTravelPlan } from './places.js'; export default function TravelPlan() { const [plan, setPlan] = useState(initialTravelPlan); function handleComplete(parentId, childId) { const parent = plan[parentId]; // Create a new version of the parent place // that doesn't include this child ID. const nextParent = { ...parent, childIds: parent.childIds .filter(id => id !== childId) }; // Update the root state object... setPlan({ ...plan, // ...so that it has the updated parent. [parentId]: nextParent }); } const root = plan[0]; const planetIds = root.childIds; return ( <> Places to visit {planetIds.map(id => ( ))} </> ); } function PlaceTree({ id, parentId, placesById, onComplete }) { const place = placesById[id]; const childIds = place.childIds; return ( {place.title} <button onClick={() => { onComplete(parentId, id); }}> Complete {childIds.length > 0 && {childIds.map(childId => ( ))} } ); } Show more You can nest state as much as you like, but making it “flat” can solve numerous problems. It makes state easier to update, and it helps ensure you don’t have duplication in different parts of a nested object. Deep DiveImproving memory usage Show DetailsIdeally, you would also remove the deleted items (and their children!) from the “table” object to improve memory usage. This version does that. It also uses Immer to make the update logic more concise.package.jsonApp.jsplaces.jspackage.jsonReloadClearFork{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} } Sometimes, you can also reduce state nesting by moving some of the nested state into the child components. This works well for ephemeral UI state that doesn’t need to be stored, like whether an item is hovered. Recap If two state variables always update together, consider merging them into one. Choose your state variables carefully to avoid creating “impossible” states. Structure your state in a way that reduces the chances that you’ll make a mistake updating it. Avoid redundant and duplicate state so that you don’t need to keep it in sync. Don’t put props into state unless you specifically want to prevent updates. For UI patterns like selection, keep ID or index in state instead of the object itself. If updating deeply nested state is complicated, try flattening it. Try out some challenges1. Fix a component that’s not updating 2. Fix a broken packing list 3. Fix the disappearing selection 4. Implement multiple selection Challenge 1 of 4: Fix a component that’s not updating This Clock component receives two props: color and time. When you select a different color in the select box, the Clock component receives a different color prop from its parent component. However, for some reason, the displayed color doesn’t update. Why? Fix the problem.Clock.jsClock.jsReloadClearForkimport { useState } from 'react'; export default function Clock(props) { const [color, setColor] = useState(props.color); return ( <h1 style={{ color: color }}> {props.time} ); } Show solutionNext ChallengePreviousReacting to Input with StateNextSharing State Between Components
const [x, setX] = useState(0);const [y, setY] = useState(0);
Pattern 5: The state used to be duplicated like this: items = [{ id: 0, title: 'pretzels'}, ...] selectedItem = {id: 0, title: 'pretzels'} But after the change it’s like this: items = [{ id: 0, title: 'pretzels'}, ...] selectedId = 0 The duplication is gone, and you only keep the essential state! Now if you edit the selected item, the message below will update immediately. This is because setItems triggers a re-render, and items.find(...) would find the item with the updated title. You didn’t need to hold the selected item in state, because only the selected ID is essential. The rest could be calculated during render. Avoid deeply nested state Imagine a travel plan consisting of planets, continents, and countries. You might be tempted to structure its state using nested objects and arrays, like in this example:
items = [{ id: 0, title: 'pretzels'}, ...]
Pattern 6: API ReferenceOverviewRules of HooksHooks are defined using JavaScript functions, but they represent a special type of reusable UI logic with restrictions on where they can be called. Only call Hooks at the top level Only call Hooks from React functions Only call Hooks at the top level Functions whose names start with use are called Hooks in React. Don’t call Hooks inside loops, conditions, nested functions, or try/catch/finally blocks. Instead, always use Hooks at the top level of your React function, before any early returns. You can only call Hooks while React is rendering a function component: ✅ Call them at the top level in the body of a function component. ✅ Call them at the top level in the body of a custom Hook. function Counter() { // ✅ Good: top-level in a function component const [count, setCount] = useState(0); // ...}function useWindowWidth() { // ✅ Good: top-level in a custom Hook const [width, setWidth] = useState(window.innerWidth); // ...} It’s not supported to call Hooks (functions starting with use) in any other cases, for example: 🔴 Do not call Hooks inside conditions or loops. 🔴 Do not call Hooks after a conditional return statement. 🔴 Do not call Hooks in event handlers. 🔴 Do not call Hooks in class components. 🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect. 🔴 Do not call Hooks inside try/catch/finally blocks. If you break these rules, you might see this error. function Bad({ cond }) { if (cond) { // 🔴 Bad: inside a condition (to fix, move it outside!) const theme = useContext(ThemeContext); } // ...}function Bad() { for (let i = 0; i < 10; i++) { // 🔴 Bad: inside a loop (to fix, move it outside!) const theme = useContext(ThemeContext); } // ...}function Bad({ cond }) { if (cond) { return; } // 🔴 Bad: after a conditional return (to fix, move it before the return!) const theme = useContext(ThemeContext); // ...}function Bad() { function handleClick() { // 🔴 Bad: inside an event handler (to fix, move it outside!) const theme = useContext(ThemeContext); } // ...}function Bad() { const style = useMemo(() => { // 🔴 Bad: inside useMemo (to fix, move it outside!) const theme = useContext(ThemeContext); return createStyle(theme); }); // ...}class Bad extends React.Component { render() { // 🔴 Bad: inside a class component (to fix, write a function component instead of a class!) useEffect(() => {}) // ... }}function Bad() { try { // 🔴 Bad: inside try/catch/finally block (to fix, move it outside!) const [x, setX] = useState(0); } catch { const [x, setX] = useState(1); }} You can use the eslint-plugin-react-hooks plugin to catch these mistakes. NoteCustom Hooks may call other Hooks (that’s their whole purpose). This works because custom Hooks are also supposed to only be called while a function component is rendering. Only call Hooks from React functions Don’t call Hooks from regular JavaScript functions. Instead, you can: ✅ Call Hooks from React function components. ✅ Call Hooks from custom Hooks. By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code. function FriendList() { const [onlineStatus, setOnlineStatus] = useOnlineStatus(); // ✅}function setOnlineStatus() { // ❌ Not a component or custom Hook! const [onlineStatus, setOnlineStatus] = useOnlineStatus();}PreviousReact calls Components and Hooks
use
Pattern 7: Learn ReactEscape HatchesReusing Logic with Custom HooksReact comes with several built-in Hooks like useState, useContext, and useEffect. Sometimes, you’ll wish that there was a Hook for some more specific purpose: for example, to fetch data, to keep track of whether the user is online, or to connect to a chat room. You might not find these Hooks in React, but you can create your own Hooks for your application’s needs. You will learn What custom Hooks are, and how to write your own How to reuse logic between components How to name and structure your custom Hooks When and why to extract custom Hooks Custom Hooks: Sharing logic between components Imagine you’re developing an app that heavily relies on the network (as most apps do). You want to warn the user if their network connection has accidentally gone off while they were using your app. How would you go about it? It seems like you’ll need two things in your component: A piece of state that tracks whether the network is online. An Effect that subscribes to the global online and offline events, and updates that state. This will keep your component synchronized with the network status. You might start with something like this: App.jsApp.jsReloadClearForkimport { useState, useEffect } from 'react'; export default function StatusBar() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { function handleOnline() { setIsOnline(true); } function handleOffline() { setIsOnline(false); } window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); return {isOnline ? '✅ Online' : '❌ Disconnected'}; } Show more Try turning your network on and off, and notice how this StatusBar updates in response to your actions. Now imagine you also want to use the same logic in a different component. You want to implement a Save button that will become disabled and show “Reconnecting…” instead of “Save” while the network is off. To start, you can copy and paste the isOnline state and the Effect into SaveButton: App.jsApp.jsReloadClearForkimport { useState, useEffect } from 'react'; export default function SaveButton() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { function handleOnline() { setIsOnline(true); } function handleOffline() { setIsOnline(false); } window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); function handleSaveClick() { console.log('✅ Progress saved'); } return ( {isOnline ? 'Save progress' : 'Reconnecting...'} ); } Show more Verify that, if you turn off the network, the button will change its appearance. These two components work fine, but the duplication in logic between them is unfortunate. It seems like even though they have different visual appearance, you want to reuse the logic between them. Extracting your own custom Hook from a component Imagine for a moment that, similar to useState and useEffect, there was a built-in useOnlineStatus Hook. Then both of these components could be simplified and you could remove the duplication between them: function StatusBar() { const isOnline = useOnlineStatus(); return {isOnline ? '✅ Online' : '❌ Disconnected'};}function SaveButton() { const isOnline = useOnlineStatus(); function handleSaveClick() { console.log('✅ Progress saved'); } return ( {isOnline ? 'Save progress' : 'Reconnecting...'} );} Although there is no such built-in Hook, you can write it yourself. Declare a function called useOnlineStatus and move all the duplicated code into it from the components you wrote earlier: function useOnlineStatus() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { function handleOnline() { setIsOnline(true); } function handleOffline() { setIsOnline(false); } window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); return isOnline;} At the end of the function, return isOnline. This lets your components read that value: App.jsuseOnlineStatus.jsApp.jsReloadClearForkimport { useOnlineStatus } from './useOnlineStatus.js'; function StatusBar() { const isOnline = useOnlineStatus(); return {isOnline ? '✅ Online' : '❌ Disconnected'}; } function SaveButton() { const isOnline = useOnlineStatus(); function handleSaveClick() { console.log('✅ Progress saved'); } return ( {isOnline ? 'Save progress' : 'Reconnecting...'} ); } export default function App() { return ( <> </> ); } Show more Verify that switching the network on and off updates both components. Now your components don’t have as much repetitive logic. More importantly, the code inside them describes what they want to do (use the online status!) rather than how to do it (by subscribing to the browser events). When you extract logic into custom Hooks, you can hide the gnarly details of how you deal with some external system or a browser API. The code of your components expresses your intent, not the implementation. Hook names always start with use React applications are built from components. Components are built from Hooks, whether built-in or custom. You’ll likely often use custom Hooks created by others, but occasionally you might write one yourself! You must follow these naming conventions: React component names must start with a capital letter, like StatusBar and SaveButton. React components also need to return something that React knows how to display, like a piece of JSX. Hook names must start with use followed by a capital letter, like useState (built-in) or useOnlineStatus (custom, like earlier on the page). Hooks may return arbitrary values. This convention guarantees that you can always look at a component and know where its state, Effects, and other React features might “hide”. For example, if you see a getColor() function call inside your component, you can be sure that it can’t possibly contain React state inside because its name doesn’t start with use. However, a function call like useOnlineStatus() will most likely contain calls to other Hooks inside! NoteIf your linter is configured for React, it will enforce this naming convention. Scroll up to the sandbox above and rename useOnlineStatus to getOnlineStatus. Notice that the linter won’t allow you to call useState or useEffect inside of it anymore. Only Hooks and components can call other Hooks! Deep DiveShould all functions called during rendering start with the use prefix? Show DetailsNo. Functions that don’t call Hooks don’t need to be Hooks.If your function doesn’t call any Hooks, avoid the use prefix. Instead, write it as a regular function without the use prefix. For example, useSorted below doesn’t call Hooks, so call it getSorted instead:// 🔴 Avoid: A Hook that doesn't use Hooksfunction useSorted(items) { return items.slice().sort();}// ✅ Good: A regular function that doesn't use Hooksfunction getSorted(items) { return items.slice().sort();}This ensures that your code can call this regular function anywhere, including conditions:function List({ items, shouldSort }) { let displayedItems = items; if (shouldSort) { // ✅ It's ok to call getSorted() conditionally because it's not a Hook displayedItems = getSorted(items); } // ...}You should give use prefix to a function (and thus make it a Hook) if it uses at least one Hook inside of it:// ✅ Good: A Hook that uses other Hooksfunction useAuth() { return useContext(Auth);}Technically, this isn’t enforced by React. In principle, you could make a Hook that doesn’t call other Hooks. This is often confusing and limiting so it’s best to avoid that pattern. However, there may be rare cases where it is helpful. For example, maybe your function doesn’t use any Hooks right now, but you plan to add some Hook calls to it in the future. Then it makes sense to name it with the use prefix:// ✅ Good: A Hook that will likely use some other Hooks laterfunction useAuth() { // TODO: Replace with this line when authentication is implemented: // return useContext(Auth); return TEST_USER;}Then components won’t be able to call it conditionally. This will become important when you actually add Hook calls inside. If you don’t plan to use Hooks inside it (now or later), don’t make it a Hook. Custom Hooks let you share stateful logic, not state itself In the earlier example, when you turned the network on and off, both components updated together. However, it’s wrong to think that a single isOnline state variable is shared between them. Look at this code: function StatusBar() { const isOnline = useOnlineStatus(); // ...}function SaveButton() { const isOnline = useOnlineStatus(); // ...} It works the same way as before you extracted the duplication: function StatusBar() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { // ... }, []); // ...}function SaveButton() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { // ... }, []); // ...} These are two completely independent state variables and Effects! They happened to have the same value at the same time because you synchronized them with the same external value (whether the network is on). To better illustrate this, we’ll need a different example. Consider this Form component: App.jsApp.jsReloadClearForkimport { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState('Mary'); const [lastName, setLastName] = useState('Poppins'); function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> First name: Last name: Good morning, {firstName} {lastName}. </> ); } Show more There’s some repetitive logic for each form field: There’s a piece of state (firstName and lastName). There’s a change handler (handleFirstNameChange and handleLastNameChange). There’s a piece of JSX that specifies the value and onChange attributes for that input. You can extract the repetitive logic into this useFormInput custom Hook: App.jsuseFormInput.jsuseFormInput.jsReloadClearForkimport { useState } from 'react'; export function useFormInput(initialValue) { const [value, setValue] = useState(initialValue); function handleChange(e) { setValue(e.target.value); } const inputProps = { value: value, onChange: handleChange }; return inputProps; } Show more Notice that it only declares one state variable called value. However, the Form component calls useFormInput two times: function Form() { const firstNameProps = useFormInput('Mary'); const lastNameProps = useFormInput('Poppins'); // ... This is why it works like declaring two separate state variables! Custom Hooks let you share stateful logic but not state itself. Each call to a Hook is completely independent from every other call to the same Hook. This is why the two sandboxes above are completely equivalent. If you’d like, scroll back up and compare them. The behavior before and after extracting a custom Hook is identical. When you need to share the state itself between multiple components, lift it up and pass it down instead. Passing reactive values between Hooks The code inside your custom Hooks will re-run during every re-render of your component. This is why, like components, custom Hooks need to be pure. Think of custom Hooks’ code as part of your component’s body! Because custom Hooks re-render together with your component, they always receive the latest props and state. To see what this means, consider this chat room example. Change the server URL or the chat room: App.jsChatRoom.jschat.jsnotifications.jsChatRoom.jsReloadClearForkimport { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; import { showNotification } from './notifications.js'; export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.on('message', (msg) => { showNotification('New message: ' + msg); }); connection.connect(); return () => connection.disconnect(); }, [roomId, serverUrl]); return ( <> Server URL: <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> Welcome to the {roomId} room! </> ); } Show more When you change serverUrl or roomId, the Effect “reacts” to your changes and re-synchronizes. You can tell by the console messages that the chat re-connects every time that you change your Effect’s dependencies. Now move the Effect’s code into a custom Hook: export function useChatRoom({ serverUrl, roomId }) { useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); connection.on('message', (msg) => { showNotification('New message: ' + msg); }); return () => connection.disconnect(); }, [roomId, serverUrl]);} This lets your ChatRoom component call your custom Hook without worrying about how it works inside: export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl }); return ( <> Server URL: <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> Welcome to the {roomId} room! </> );} This looks much simpler! (But it does the same thing.) Notice that the logic still responds to prop and state changes. Try editing the server URL or the selected room: App.jsChatRoom.jsuseChatRoom.jschat.jsnotifications.jsChatRoom.jsReloadClearForkimport { useState } from 'react'; import { useChatRoom } from './useChatRoom.js'; export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl }); return ( <> Server URL: <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> Welcome to the {roomId} room! </> ); } Show more Notice how you’re taking the return value of one Hook: export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl }); // ... and passing it as an input to another Hook: export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl }); // ... Every time your ChatRoom component re-renders, it passes the latest roomId and serverUrl to your Hook. This is why your Effect re-connects to the chat whenever their values are different after a re-render. (If you ever worked with audio or video processing software, chaining Hooks like this might remind you of chaining visual or audio effects. It’s as if the output of useState “feeds into” the input of the useChatRoom.) Passing event handlers to custom Hooks As you start using useChatRoom in more components, you might want to let components customize its behavior. For example, currently, the logic for what to do when a message arrives is hardcoded inside the Hook: export function useChatRoom({ serverUrl, roomId }) { useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); connection.on('message', (msg) => { showNotification('New message: ' + msg); }); return () => connection.disconnect(); }, [roomId, serverUrl]);} Let’s say you want to move this logic back to your component: export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl, onReceiveMessage(msg) { showNotification('New message: ' + msg); } }); // ... To make this work, change your custom Hook to take onReceiveMessage as one of its named options: export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) { useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); connection.on('message', (msg) => { onReceiveMessage(msg); }); return () => connection.disconnect(); }, [roomId, serverUrl, onReceiveMessage]); // ✅ All dependencies declared} This will work, but there’s one more improvement you can do when your custom Hook accepts event handlers. Adding a dependency on onReceiveMessage is not ideal because it will cause the chat to re-connect every time the component re-renders. Wrap this event handler into an Effect Event to remove it from the dependencies: import { useEffect, useEffectEvent } from 'react';// ...export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) { const onMessage = useEffectEvent(onReceiveMessage); useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); connection.on('message', (msg) => { onMessage(msg); }); return () => connection.disconnect(); }, [roomId, serverUrl]); // ✅ All dependencies declared} Now the chat won’t re-connect every time that the ChatRoom component re-renders. Here is a fully working demo of passing an event handler to a custom Hook that you can play with: App.jsChatRoom.jsuseChatRoom.jschat.jsnotifications.jsChatRoom.jsReloadClearForkimport { useState } from 'react'; import { useChatRoom } from './useChatRoom.js'; import { showNotification } from './notifications.js'; export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl, onReceiveMessage(msg) { showNotification('New message: ' + msg); } }); return ( <> Server URL: <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> Welcome to the {roomId} room! </> ); } Show more Notice how you no longer need to know how useChatRoom works in order to use it. You could add it to any other component, pass any other options, and it would work the same way. That’s the power of custom Hooks. When to use custom Hooks You don’t need to extract a custom Hook for every little duplicated bit of code. Some duplication is fine. For example, extracting a useFormInput Hook to wrap a single useState call like earlier is probably unnecessary. However, whenever you write an Effect, consider whether it would be clearer to also wrap it in a custom Hook. You shouldn’t need Effects very often, so if you’re writing one, it means that you need to “step outside React” to synchronize with some external system or to do something that React doesn’t have a built-in API for. Wrapping it into a custom Hook lets you precisely communicate your intent and how the data flows through it. For example, consider a ShippingForm component that displays two dropdowns: one shows the list of cities, and another shows the list of areas in the selected city. You might start with some code that looks like this: function ShippingForm({ country }) { const [cities, setCities] = useState(null); // This Effect fetches cities for a country useEffect(() => { let ignore = false; fetch(/api/cities?country=${country} ) .then(response => response.json()) .then(json => { if (!ignore) { setCities(json); } }); return () => { ignore = true; }; }, [country]); const [city, setCity] = useState(null); const [areas, setAreas] = useState(null); // This Effect fetches areas for the selected city useEffect(() => { if (city) { let ignore = false; fetch(/api/areas?city=${city} ) .then(response => response.json()) .then(json => { if (!ignore) { setAreas(json); } }); return () => { ignore = true; }; } }, [city]); // ... Although this code is quite repetitive, it’s correct to keep these Effects separate from each other. They synchronize two different things, so you shouldn’t merge them into one Effect. Instead, you can simplify the ShippingForm component above by extracting the common logic between them into your own useData Hook: function useData(url) { const [data, setData] = useState(null); useEffect(() => { if (url) { let ignore = false; fetch(url) .then(response => response.json()) .then(json => { if (!ignore) { setData(json); } }); return () => { ignore = true; }; } }, [url]); return data;} Now you can replace both Effects in the ShippingForm components with calls to useData: function ShippingForm({ country }) { const cities = useData(/api/cities?country=${country} ); const [city, setCity] = useState(null); const areas = useData(city ? /api/areas?city=${city} : null); // ... Extracting a custom Hook makes the data flow explicit. You feed the url in and you get the data out. By “hiding” your Effect inside useData, you also prevent someone working on the ShippingForm component from adding unnecessary dependencies to it. With time, most of your app’s Effects will be in custom Hooks. Deep DiveKeep your custom Hooks focused on concrete high-level use cases Show DetailsStart by choosing your custom Hook’s name. If you struggle to pick a clear name, it might mean that your Effect is too coupled to the rest of your component’s logic, and is not yet ready to be extracted.Ideally, your custom Hook’s name should be clear enough that even a person who doesn’t write code often could have a good guess about what your custom Hook does, what it takes, and what it returns: ✅ useData(url) ✅ useImpressionLog(eventName, extraData) ✅ useChatRoom(options) When you synchronize with an external system, your custom Hook name may be more technical and use jargon specific to that system. It’s good as long as it would be clear to a person familiar with that system: ✅ useMediaQuery(query) ✅ useSocket(url) ✅ useIntersectionObserver(ref, options) Keep custom Hooks focused on concrete high-level use cases. Avoid creating and using custom “lifecycle” Hooks that act as alternatives and convenience wrappers for the useEffect API itself: 🔴 useMount(fn) 🔴 useEffectOnce(fn) 🔴 useUpdateEffect(fn) For example, this useMount Hook tries to ensure some code only runs “on mount”:function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // 🔴 Avoid: using custom "lifecycle" Hooks useMount(() => { const connection = createConnection({ roomId, serverUrl }); connection.connect(); post('/analytics/event', { eventName: 'visit_chat' }); }); // ...}// 🔴 Avoid: creating custom "lifecycle" Hooksfunction useMount(fn) { useEffect(() => { fn(); }, []); // 🔴 React Hook useEffect has a missing dependency: 'fn'}Custom “lifecycle” Hooks like useMount don’t fit well into the React paradigm. For example, this code example has a mistake (it doesn’t “react” to roomId or serverUrl changes), but the linter won’t warn you about it because the linter only checks direct useEffect calls. It won’t know about your Hook.If you’re writing an Effect, start by using the React API directly:function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // ✅ Good: two raw Effects separated by purpose useEffect(() => { const connection = createConnection({ serverUrl, roomId }); connection.connect(); return () => connection.disconnect(); }, [serverUrl, roomId]); useEffect(() => { post('/analytics/event', { eventName: 'visit_chat', roomId }); }, [roomId]); // ...}Then, you can (but don’t have to) extract custom Hooks for different high-level use cases:function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // ✅ Great: custom Hooks named after their purpose useChatRoom({ serverUrl, roomId }); useImpressionLog('visit_chat', { roomId }); // ...}A good custom Hook makes the calling code more declarative by constraining what it does. For example, useChatRoom(options) can only connect to the chat room, while useImpressionLog(eventName, extraData) can only send an impression log to the analytics. If your custom Hook API doesn’t constrain the use cases and is very abstract, in the long run it’s likely to introduce more problems than it solves. Custom Hooks help you migrate to better patterns Effects are an “escape hatch”: you use them when you need to “step outside React” and when there is no better built-in solution for your use case. With time, the React team’s goal is to reduce the number of the Effects in your app to the minimum by providing more specific solutions to more specific problems. Wrapping your Effects in custom Hooks makes it easier to upgrade your code when these solutions become available. Let’s return to this example: App.jsuseOnlineStatus.jsuseOnlineStatus.jsReloadClearForkimport { useState, useEffect } from 'react'; export function useOnlineStatus() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { function handleOnline() { setIsOnline(true); } function handleOffline() { setIsOnline(false); } window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); return isOnline; } Show more In the above example, useOnlineStatus is implemented with a pair of useState and useEffect. However, this isn’t the best possible solution. There is a number of edge cases it doesn’t consider. For example, it assumes that when the component mounts, isOnline is already true, but this may be wrong if the network already went offline. You can use the browser navigator.onLine API to check for that, but using it directly would not work on the server for generating the initial HTML. In short, this code could be improved. React includes a dedicated API called useSyncExternalStore which takes care of all of these problems for you. Here is your useOnlineStatus Hook, rewritten to take advantage of this new API: App.jsuseOnlineStatus.jsuseOnlineStatus.jsReloadClearForkimport { useSyncExternalStore } from 'react'; function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; } export function useOnlineStatus() { return useSyncExternalStore( subscribe, () => navigator.onLine, // How to get the value on the client () => true // How to get the value on the server ); } Show more Notice how you didn’t need to change any of the components to make this migration: function StatusBar() { const isOnline = useOnlineStatus(); // ...}function SaveButton() { const isOnline = useOnlineStatus(); // ...} This is another reason for why wrapping Effects in custom Hooks is often beneficial: You make the data flow to and from your Effects very explicit. You let your components focus on the intent rather than on the exact implementation of your Effects. When React adds new features, you can remove those Effects without changing any of your components. Similar to a design system, you might find it helpful to start extracting common idioms from your app’s components into custom Hooks. This will keep your components’ code focused on the intent, and let you avoid writing raw Effects very often. Many excellent custom Hooks are maintained by the React community. Deep DiveWill React provide any built-in solution for data fetching? Show DetailsToday, with the use API, data can be read in render by passing a Promise to use:import { use, Suspense } from "react";function Message({ messagePromise }) { const messageContent = use(messagePromise); return Here is the message: {messageContent};}export function MessageContainer({ messagePromise }) { return ( <Suspense fallback={⌛Downloading message...}> );}We’re still working out the details, but we expect that in the future, you’ll write data fetching like this:import { use } from 'react';function ShippingForm({ country }) { const cities = use(fetch(/api/cities?country=${country} )); const [city, setCity] = useState(null); const areas = city ? use(fetch(/api/areas?city=${city} )) : null; // ...If you use custom Hooks like useData above in your app, it will require fewer changes to migrate to the eventually recommended approach than if you write raw Effects in every component manually. However, the old approach will still work fine, so if you feel happy writing raw Effects, you can continue to do that. There is more than one way to do it Let’s say you want to implement a fade-in animation from scratch using the browser requestAnimationFrame API. You might start with an Effect that sets up an animation loop. During each frame of the animation, you could change the opacity of the DOM node you hold in a ref until it reaches 1. Your code might start like this: App.jsApp.jsReloadClearForkimport { useState, useEffect, useRef } from 'react'; function Welcome() { const ref = useRef(null); useEffect(() => { const duration = 1000; const node = ref.current; let startTime = performance.now(); let frameId = null; function onFrame(now) { const timePassed = now - startTime; const progress = Math.min(timePassed / duration, 1); onProgress(progress); if (progress < 1) { // We still have more frames to paint frameId = requestAnimationFrame(onFrame); } } function onProgress(progress) { node.style.opacity = progress; } function start() { onProgress(0); startTime = performance.now(); frameId = requestAnimationFrame(onFrame); } function stop() { cancelAnimationFrame(frameId); startTime = null; frameId = null; } start(); return () => stop(); }, []); return ( Welcome ); } export default function App() { const [show, setShow] = useState(false); return ( <> <button onClick={() => setShow(!show)}> {show ? 'Remove' : 'Show'} {show && } </> ); } Show more To make the component more readable, you might extract the logic into a useFadeIn custom Hook: App.jsuseFadeIn.jsApp.jsReloadClearForkimport { useState, useEffect, useRef } from 'react'; import { useFadeIn } from './useFadeIn.js'; function Welcome() { const ref = useRef(null); useFadeIn(ref, 1000); return ( Welcome ); } export default function App() { const [show, setShow] = useState(false); return ( <> <button onClick={() => setShow(!show)}> {show ? 'Remove' : 'Show'} {show && } </> ); } Show more You could keep the useFadeIn code as is, but you could also refactor it more. For example, you could extract the logic for setting up the animation loop out of useFadeIn into a custom useAnimationLoop Hook: App.jsuseFadeIn.jsuseFadeIn.jsReloadClearForkimport { useState, useEffect } from 'react'; import { useEffectEvent } from 'react'; export function useFadeIn(ref, duration) { const [isRunning, setIsRunning] = useState(true); useAnimationLoop(isRunning, (timePassed) => { const progress = Math.min(timePassed / duration, 1); ref.current.style.opacity = progress; if (progress === 1) { setIsRunning(false); } }); } function useAnimationLoop(isRunning, drawFrame) { const onFrame = useEffectEvent(drawFrame); useEffect(() => { if (!isRunning) { return; } const startTime = performance.now(); let frameId = null; function tick(now) { const timePassed = now - startTime; onFrame(timePassed); frameId = requestAnimationFrame(tick); } tick(); return () => cancelAnimationFrame(frameId); }, [isRunning]); } Show more However, you didn’t have to do that. As with regular functions, ultimately you decide where to draw the boundaries between different parts of your code. You could also take a very different approach. Instead of keeping the logic in the Effect, you could move most of the imperative logic inside a JavaScript class: App.jsuseFadeIn.jsanimation.jsuseFadeIn.jsReloadClearForkimport { useState, useEffect } from 'react'; import { FadeInAnimation } from './animation.js'; export function useFadeIn(ref, duration) { useEffect(() => { const animation = new FadeInAnimation(ref.current); animation.start(duration); return () => { animation.stop(); }; }, [ref, duration]); } Effects let you connect React to external systems. The more coordination between Effects is needed (for example, to chain multiple animations), the more it makes sense to extract that logic out of Effects and Hooks completely like in the sandbox above. Then, the code you extracted becomes the “external system”. This lets your Effects stay simple because they only need to send messages to the system you’ve moved outside React. The examples above assume that the fade-in logic needs to be written in JavaScript. However, this particular fade-in animation is both simpler and much more efficient to implement with a plain CSS Animation: App.jswelcome.csswelcome.cssReloadClearFork.welcome { color: white; padding: 50px; text-align: center; font-size: 50px; background-image: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); animation: fadeIn 1000ms; } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } Sometimes, you don’t even need a Hook! Recap Custom Hooks let you share logic between components. Custom Hooks must be named starting with use followed by a capital letter. Custom Hooks only share stateful logic, not state itself. You can pass reactive values from one Hook to another, and they stay up-to-date. All Hooks re-run every time your component re-renders. The code of your custom Hooks should be pure, like your component’s code. Wrap event handlers received by custom Hooks into Effect Events. Don’t create custom Hooks like useMount. Keep their purpose specific. It’s up to you how and where to choose the boundaries of your code. Try out some challenges1. Extract a useCounter Hook 2. Make the counter delay configurable 3. Extract useInterval out of useCounter 4. Fix a resetting interval 5. Implement a staggering movement Challenge 1 of 5: Extract a useCounter Hook This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called useCounter. Your goal is to make the Counter component implementation look exactly like this:export default function Counter() { const count = useCounter(); return Seconds passed: {count};}You’ll need to write your custom Hook in useCounter.js and import it into the App.js file.App.jsuseCounter.jsApp.jsReloadClearForkimport { useState, useEffect } from 'react'; export default function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(id); }, []); return Seconds passed: {count}; } Show solutionNext ChallengePreviousRemoving Effect Dependencies
useState
Pattern 8: Notice how you no longer need to know how useChatRoom works in order to use it. You could add it to any other component, pass any other options, and it would work the same way. That’s the power of custom Hooks. When to use custom Hooks You don’t need to extract a custom Hook for every little duplicated bit of code. Some duplication is fine. For example, extracting a useFormInput Hook to wrap a single useState call like earlier is probably unnecessary. However, whenever you write an Effect, consider whether it would be clearer to also wrap it in a custom Hook. You shouldn’t need Effects very often, so if you’re writing one, it means that you need to “step outside React” to synchronize with some external system or to do something that React doesn’t have a built-in API for. Wrapping it into a custom Hook lets you precisely communicate your intent and how the data flows through it. For example, consider a ShippingForm component that displays two dropdowns: one shows the list of cities, and another shows the list of areas in the selected city. You might start with some code that looks like this: function ShippingForm({ country }) { const [cities, setCities] = useState(null); // This Effect fetches cities for a country useEffect(() => { let ignore = false; fetch(/api/cities?country=${country} ) .then(response => response.json()) .then(json => { if (!ignore) { setCities(json); } }); return () => { ignore = true; }; }, [country]); const [city, setCity] = useState(null); const [areas, setAreas] = useState(null); // This Effect fetches areas for the selected city useEffect(() => { if (city) { let ignore = false; fetch(/api/areas?city=${city} ) .then(response => response.json()) .then(json => { if (!ignore) { setAreas(json); } }); return () => { ignore = true; }; } }, [city]); // ... Although this code is quite repetitive, it’s correct to keep these Effects separate from each other. They synchronize two different things, so you shouldn’t merge them into one Effect. Instead, you can simplify the ShippingForm component above by extracting the common logic between them into your own useData Hook: function useData(url) { const [data, setData] = useState(null); useEffect(() => { if (url) { let ignore = false; fetch(url) .then(response => response.json()) .then(json => { if (!ignore) { setData(json); } }); return () => { ignore = true; }; } }, [url]); return data;} Now you can replace both Effects in the ShippingForm components with calls to useData: function ShippingForm({ country }) { const cities = useData(/api/cities?country=${country} ); const [city, setCity] = useState(null); const areas = useData(city ? /api/areas?city=${city} : null); // ... Extracting a custom Hook makes the data flow explicit. You feed the url in and you get the data out. By “hiding” your Effect inside useData, you also prevent someone working on the ShippingForm component from adding unnecessary dependencies to it. With time, most of your app’s Effects will be in custom Hooks. Deep DiveKeep your custom Hooks focused on concrete high-level use cases Show DetailsStart by choosing your custom Hook’s name. If you struggle to pick a clear name, it might mean that your Effect is too coupled to the rest of your component’s logic, and is not yet ready to be extracted.Ideally, your custom Hook’s name should be clear enough that even a person who doesn’t write code often could have a good guess about what your custom Hook does, what it takes, and what it returns: ✅ useData(url) ✅ useImpressionLog(eventName, extraData) ✅ useChatRoom(options) When you synchronize with an external system, your custom Hook name may be more technical and use jargon specific to that system. It’s good as long as it would be clear to a person familiar with that system: ✅ useMediaQuery(query) ✅ useSocket(url) ✅ useIntersectionObserver(ref, options) Keep custom Hooks focused on concrete high-level use cases. Avoid creating and using custom “lifecycle” Hooks that act as alternatives and convenience wrappers for the useEffect API itself: 🔴 useMount(fn) 🔴 useEffectOnce(fn) 🔴 useUpdateEffect(fn) For example, this useMount Hook tries to ensure some code only runs “on mount”:function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // 🔴 Avoid: using custom "lifecycle" Hooks useMount(() => { const connection = createConnection({ roomId, serverUrl }); connection.connect(); post('/analytics/event', { eventName: 'visit_chat' }); }); // ...}// 🔴 Avoid: creating custom "lifecycle" Hooksfunction useMount(fn) { useEffect(() => { fn(); }, []); // 🔴 React Hook useEffect has a missing dependency: 'fn'}Custom “lifecycle” Hooks like useMount don’t fit well into the React paradigm. For example, this code example has a mistake (it doesn’t “react” to roomId or serverUrl changes), but the linter won’t warn you about it because the linter only checks direct useEffect calls. It won’t know about your Hook.If you’re writing an Effect, start by using the React API directly:function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // ✅ Good: two raw Effects separated by purpose useEffect(() => { const connection = createConnection({ serverUrl, roomId }); connection.connect(); return () => connection.disconnect(); }, [serverUrl, roomId]); useEffect(() => { post('/analytics/event', { eventName: 'visit_chat', roomId }); }, [roomId]); // ...}Then, you can (but don’t have to) extract custom Hooks for different high-level use cases:function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // ✅ Great: custom Hooks named after their purpose useChatRoom({ serverUrl, roomId }); useImpressionLog('visit_chat', { roomId }); // ...}A good custom Hook makes the calling code more declarative by constraining what it does. For example, useChatRoom(options) can only connect to the chat room, while useImpressionLog(eventName, extraData) can only send an impression log to the analytics. If your custom Hook API doesn’t constrain the use cases and is very abstract, in the long run it’s likely to introduce more problems than it solves. Custom Hooks help you migrate to better patterns Effects are an “escape hatch”: you use them when you need to “step outside React” and when there is no better built-in solution for your use case. With time, the React team’s goal is to reduce the number of the Effects in your app to the minimum by providing more specific solutions to more specific problems. Wrapping your Effects in custom Hooks makes it easier to upgrade your code when these solutions become available. Let’s return to this example:
useChatRoom
Example Code Patterns
Example 1 (javascript):
const [state, setState] = useState(initialState)
Example 2 (javascript):
const [state, setState] = useState(initialState)
Example 3 (javascript):
function FilterableProductTable({ products }) { const [filterText, setFilterText] = useState(''); const [inStockOnly, setInStockOnly] = useState(false);
Example 4 (javascript):
const [state, dispatch] = useReducer(reducer, initialArg, init?)
Example 5 (python):
import { useReducer } from 'react';function reducer(state, action) { // ...}function MyComponent() { const [state, dispatch] = useReducer(reducer, { age: 42 }); // ...
Reference Files
This skill includes comprehensive documentation in references/ :
-
api.md - Api documentation
-
components.md - Components documentation
-
getting_started.md - Getting Started documentation
-
hooks.md - Hooks documentation
-
other.md - Other documentation
-
state.md - State documentation
Use view to read specific reference files when detailed information is needed.
Working with This Skill
For Beginners
Start with the getting_started or tutorials reference files for foundational concepts.
For Specific Features
Use the appropriate category reference file (api, guides, etc.) for detailed information.
For Code Examples
The quick reference section above contains common patterns extracted from the official docs.
Resources
references/
Organized documentation extracted from official sources. These files contain:
-
Detailed explanations
-
Code examples with language annotations
-
Links to original documentation
-
Table of contents for quick navigation
scripts/
Add helper scripts here for common automation tasks.
assets/
Add templates, boilerplate, or example projects here.
Notes
-
This skill was automatically generated from official documentation
-
Reference files preserve the structure and examples from source docs
-
Code examples include language detection for better syntax highlighting
-
Quick reference patterns are extracted from common usage examples in the docs
Updating
To refresh this skill with updated documentation:
-
Re-run the scraper with the same configuration
-
The skill will be rebuilt with the latest information