| name | react |
| description | React framework for building user interfaces. Use for React components, hooks, state management, JSX, and modern frontend development. |
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
Thanks for feedback!
} return (How was your stay at The Prancing Pony?
<textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} />{isSending &&
Sending...
}Thanks for feedback!
} return (How was your stay at The Prancing Pony?
<textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} />{isSending &&
Sending...
}Let’s check you in
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
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 }) { // Thecolor 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 => (
- {childIds.map(childId => (
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 ( ); } 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 ( );} 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 ( ); } export default function App() { return ( <>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 ( <>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 ( <>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 ( <>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 ( <>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...
}>/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 &&
Welcome
); } export default function App() { const [show, setShow] = useState(false); return ( <> <button onClick={() => setShow(!show)}> {show ? 'Remove' : 'Show'}{show &&
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); }, []); returnSeconds passed: {count}
; } Show solutionNext ChallengePreviousRemoving Effect DependenciesuseState
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