Movatterモバイル変換


[0]ホーム

URL:


Is this page useful?

Reusing Logic with Custom Hooks

React comes with several built-in Hooks likeuseState,useContext, anduseEffect. 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:

  1. A piece of state that tracks whether the network is online.
  2. An Effect that subscribes to the globalonline andoffline events, and updates that state.

This will keep your componentsynchronized with the network status. You might start with something like this:

Fork
import{useState,useEffect}from'react';exportdefaultfunctionStatusBar(){const[isOnline,setIsOnline] =useState(true);useEffect(()=>{functionhandleOnline(){setIsOnline(true);}functionhandleOffline(){setIsOnline(false);}window.addEventListener('online',handleOnline);window.addEventListener('offline',handleOffline);return()=>{window.removeEventListener('online',handleOnline);window.removeEventListener('offline',handleOffline);};},[]);return<h1>{isOnline ?'✅ Online' :'❌ Disconnected'}</h1>;}

Try turning your network on and off, and notice how thisStatusBar updates in response to your actions.

Now imagine youalso 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 theisOnline state and the Effect intoSaveButton:

Fork
import{useState,useEffect}from'react';exportdefaultfunctionSaveButton(){const[isOnline,setIsOnline] =useState(true);useEffect(()=>{functionhandleOnline(){setIsOnline(true);}functionhandleOffline(){setIsOnline(false);}window.addEventListener('online',handleOnline);window.addEventListener('offline',handleOffline);return()=>{window.removeEventListener('online',handleOnline);window.removeEventListener('offline',handleOffline);};},[]);functionhandleSaveClick(){console.log('✅ Progress saved');}return(<buttondisabled={!isOnline}onClick={handleSaveClick}>{isOnline ?'Save progress' :'Reconnecting...'}</button>);}

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 differentvisual appearance, you want to reuse the logic between them.

Extracting your own custom Hook from a component

Imagine for a moment that, similar touseState anduseEffect, there was a built-inuseOnlineStatus Hook. Then both of these components could be simplified and you could remove the duplication between them:

functionStatusBar(){
constisOnline =useOnlineStatus();
return<h1>{isOnline ?'✅ Online' :'❌ Disconnected'}</h1>;
}

functionSaveButton(){
constisOnline =useOnlineStatus();

functionhandleSaveClick(){
console.log('✅ Progress saved');
}

return(
<buttondisabled={!isOnline}onClick={handleSaveClick}>
{isOnline ?'Save progress' :'Reconnecting...'}
</button>
);
}

Although there is no such built-in Hook, you can write it yourself. Declare a function calleduseOnlineStatus and move all the duplicated code into it from the components you wrote earlier:

functionuseOnlineStatus(){
const[isOnline,setIsOnline] =useState(true);
useEffect(()=>{
functionhandleOnline(){
setIsOnline(true);
}
functionhandleOffline(){
setIsOnline(false);
}
window.addEventListener('online',handleOnline);
window.addEventListener('offline',handleOffline);
return()=>{
window.removeEventListener('online',handleOnline);
window.removeEventListener('offline',handleOffline);
};
},[]);
returnisOnline;
}

At the end of the function, returnisOnline. This lets your components read that value:

Fork
import{useOnlineStatus}from'./useOnlineStatus.js';functionStatusBar(){constisOnline =useOnlineStatus();return<h1>{isOnline ?'✅ Online' :'❌ Disconnected'}</h1>;}functionSaveButton(){constisOnline =useOnlineStatus();functionhandleSaveClick(){console.log('✅ Progress saved');}return(<buttondisabled={!isOnline}onClick={handleSaveClick}>{isOnline ?'Save progress' :'Reconnecting...'}</button>);}exportdefaultfunctionApp(){return(<><SaveButton/><StatusBar/></>);}

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 describeswhat they want to do (use the online status!) rather thanhow 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 withuse

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:

  1. React component names must start with a capital letter, likeStatusBar andSaveButton. React components also need to return something that React knows how to display, like a piece of JSX.
  2. Hook names must start withuse followed by a capital letter, likeuseState (built-in) oruseOnlineStatus (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 agetColor() function call inside your component, you can be sure that it can’t possibly contain React state inside because its name doesn’t start withuse. However, a function call likeuseOnlineStatus() will most likely contain calls to other Hooks inside!

Note

If your linter isconfigured for React, it will enforce this naming convention. Scroll up to the sandbox above and renameuseOnlineStatus togetOnlineStatus. Notice that the linter won’t allow you to calluseState oruseEffect inside of it anymore. Only Hooks and components can call other Hooks!

Deep Dive

Should all functions called during rendering start with the use prefix?

No. Functions that don’tcall Hooks don’t need tobe Hooks.

If your function doesn’t call any Hooks, avoid theuse prefix. Instead, write it as a regular functionwithout theuse prefix. For example,useSorted below doesn’t call Hooks, so call itgetSorted instead:

// 🔴 Avoid: A Hook that doesn't use Hooks
functionuseSorted(items){
returnitems.slice().sort();
}

// ✅ Good: A regular function that doesn't use Hooks
functiongetSorted(items){
returnitems.slice().sort();
}

This ensures that your code can call this regular function anywhere, including conditions:

functionList({items,shouldSort}){
letdisplayedItems =items;
if(shouldSort){
// ✅ It's ok to call getSorted() conditionally because it's not a Hook
displayedItems =getSorted(items);
}
// ...
}

You should giveuse 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 Hooks
functionuseAuth(){
returnuseContext(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 theuse prefix:

// ✅ Good: A Hook that will likely use some other Hooks later
functionuseAuth(){
// TODO: Replace with this line when authentication is implemented:
// return useContext(Auth);
returnTEST_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 singleisOnline state variable is shared between them. Look at this code:

functionStatusBar(){
constisOnline =useOnlineStatus();
// ...
}

functionSaveButton(){
constisOnline =useOnlineStatus();
// ...
}

It works the same way as before you extracted the duplication:

functionStatusBar(){
const[isOnline,setIsOnline] =useState(true);
useEffect(()=>{
// ...
},[]);
// ...
}

functionSaveButton(){
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 thisForm component:

Fork
import{useState}from'react';exportdefaultfunctionForm(){const[firstName,setFirstName] =useState('Mary');const[lastName,setLastName] =useState('Poppins');functionhandleFirstNameChange(e){setFirstName(e.target.value);}functionhandleLastNameChange(e){setLastName(e.target.value);}return(<><label>        First name:<inputvalue={firstName}onChange={handleFirstNameChange}/></label><label>        Last name:<inputvalue={lastName}onChange={handleLastNameChange}/></label><p><b>Good morning,{firstName}{lastName}.</b></p></>);}

There’s some repetitive logic for each form field:

  1. There’s a piece of state (firstName andlastName).
  2. There’s a change handler (handleFirstNameChange andhandleLastNameChange).
  3. There’s a piece of JSX that specifies thevalue andonChange attributes for that input.

You can extract the repetitive logic into thisuseFormInput custom Hook:

Fork
import{useState}from'react';exportfunctionuseFormInput(initialValue){const[value,setValue] =useState(initialValue);functionhandleChange(e){setValue(e.target.value);}constinputProps ={value:value,onChange:handleChange};returninputProps;}

Notice that it only declaresone state variable calledvalue.

However, theForm component callsuseFormInputtwo times:

functionForm(){
constfirstNameProps =useFormInput('Mary');
constlastNameProps =useFormInput('Poppins');
// ...

This is why it works like declaring two separate state variables!

Custom Hooks let you sharestateful logic but notstate 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 Hooksneed 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:

Fork
import{useState,useEffect}from'react';import{createConnection}from'./chat.js';import{showNotification}from'./notifications.js';exportdefaultfunctionChatRoom({roomId}){const[serverUrl,setServerUrl] =useState('https://localhost:1234');useEffect(()=>{constoptions ={serverUrl:serverUrl,roomId:roomId};constconnection =createConnection(options);connection.on('message',(msg)=>{showNotification('New message: ' +msg);});connection.connect();return()=>connection.disconnect();},[roomId,serverUrl]);return(<><label>        Server URL:<inputvalue={serverUrl}onChange={e=>setServerUrl(e.target.value)}/></label><h1>Welcome to the{roomId} room!</h1></>);}

When you changeserverUrl orroomId, 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:

exportfunctionuseChatRoom({serverUrl,roomId}){
useEffect(()=>{
constoptions ={
serverUrl:serverUrl,
roomId:roomId
};
constconnection =createConnection(options);
connection.connect();
connection.on('message',(msg)=>{
showNotification('New message: ' +msg);
});
return()=>connection.disconnect();
},[roomId,serverUrl]);
}

This lets yourChatRoom component call your custom Hook without worrying about how it works inside:

exportdefaultfunctionChatRoom({roomId}){
const[serverUrl,setServerUrl] =useState('https://localhost:1234');

useChatRoom({
roomId:roomId,
serverUrl:serverUrl
});

return(
<>
<label>
Server URL:
<inputvalue={serverUrl}onChange={e=>setServerUrl(e.target.value)}/>
</label>
<h1>Welcome to the{roomId} room!</h1>
</>
);
}

This looks much simpler! (But it does the same thing.)

Notice that the logicstill responds to prop and state changes. Try editing the server URL or the selected room:

Fork
import{useState}from'react';import{useChatRoom}from'./useChatRoom.js';exportdefaultfunctionChatRoom({roomId}){const[serverUrl,setServerUrl] =useState('https://localhost:1234');useChatRoom({roomId:roomId,serverUrl:serverUrl});return(<><label>        Server URL:<inputvalue={serverUrl}onChange={e=>setServerUrl(e.target.value)}/></label><h1>Welcome to the{roomId} room!</h1></>);}

Notice how you’re taking the return value of one Hook:

exportdefaultfunctionChatRoom({roomId}){
const[serverUrl,setServerUrl] =useState('https://localhost:1234');

useChatRoom({
roomId:roomId,
serverUrl:serverUrl
});
// ...

and passing it as an input to another Hook:

exportdefaultfunctionChatRoom({roomId}){
const[serverUrl,setServerUrl] =useState('https://localhost:1234');

useChatRoom({
roomId:roomId,
serverUrl:serverUrl
});
// ...

Every time yourChatRoom component re-renders, it passes the latestroomId andserverUrl 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 ofuseState “feeds into” the input of theuseChatRoom.)

Passing event handlers to custom Hooks

As you start usinguseChatRoom 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:

exportfunctionuseChatRoom({serverUrl,roomId}){
useEffect(()=>{
constoptions ={
serverUrl:serverUrl,
roomId:roomId
};
constconnection =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:

exportdefaultfunctionChatRoom({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 takeonReceiveMessage as one of its named options:

exportfunctionuseChatRoom({serverUrl,roomId,onReceiveMessage}){
useEffect(()=>{
constoptions ={
serverUrl:serverUrl,
roomId:roomId
};
constconnection =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 ononReceiveMessage 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';
// ...

exportfunctionuseChatRoom({serverUrl,roomId,onReceiveMessage}){
constonMessage =useEffectEvent(onReceiveMessage);

useEffect(()=>{
constoptions ={
serverUrl:serverUrl,
roomId:roomId
};
constconnection =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 theChatRoom component re-renders. Here is a fully working demo of passing an event handler to a custom Hook that you can play with:

Fork
import{useState}from'react';import{useChatRoom}from'./useChatRoom.js';import{showNotification}from'./notifications.js';exportdefaultfunctionChatRoom({roomId}){const[serverUrl,setServerUrl] =useState('https://localhost:1234');useChatRoom({roomId:roomId,serverUrl:serverUrl,onReceiveMessage(msg){showNotification('New message: ' +msg);}});return(<><label>        Server URL:<inputvalue={serverUrl}onChange={e=>setServerUrl(e.target.value)}/></label><h1>Welcome to the{roomId} room!</h1></>);}

Notice how you no longer need to knowhowuseChatRoom 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 auseFormInput Hook to wrap a singleuseState 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 aShippingForm 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:

functionShippingForm({country}){
const[cities,setCities] =useState(null);
// This Effect fetches cities for a country
useEffect(()=>{
letignore =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){
letignore =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 theShippingForm component above by extracting the common logic between them into your ownuseData Hook:

functionuseData(url){
const[data,setData] =useState(null);
useEffect(()=>{
if(url){
letignore =false;
fetch(url)
.then(response=>response.json())
.then(json=>{
if(!ignore){
setData(json);
}
});
return()=>{
ignore =true;
};
}
},[url]);
returndata;
}

Now you can replace both Effects in theShippingForm components with calls touseData:

functionShippingForm({country}){
constcities =useData(`/api/cities?country=${country}`);
const[city,setCity] =useState(null);
constareas =useData(city ?`/api/areas?city=${city}` :null);
// ...

Extracting a custom Hook makes the data flow explicit. You feed theurl in and you get thedata out. By “hiding” your Effect insideuseData, you also prevent someone working on theShippingForm component from addingunnecessary dependencies to it. With time, most of your app’s Effects will be in custom Hooks.

Deep Dive

Keep your custom Hooks focused on concrete high-level use cases

Start 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 theuseEffect API itself:

  • 🔴useMount(fn)
  • 🔴useEffectOnce(fn)
  • 🔴useUpdateEffect(fn)

For example, thisuseMount Hook tries to ensure some code only runs “on mount”:

functionChatRoom({roomId}){
const[serverUrl,setServerUrl] =useState('https://localhost:1234');

// 🔴 Avoid: using custom "lifecycle" Hooks
useMount(()=>{
constconnection =createConnection({roomId,serverUrl});
connection.connect();

post('/analytics/event',{eventName:'visit_chat'});
});
// ...
}

// 🔴 Avoid: creating custom "lifecycle" Hooks
functionuseMount(fn){
useEffect(()=>{
fn();
},[]);// 🔴 React Hook useEffect has a missing dependency: 'fn'
}

Custom “lifecycle” Hooks likeuseMount don’t fit well into the React paradigm. For example, this code example has a mistake (it doesn’t “react” toroomId orserverUrl changes), but the linter won’t warn you about it because the linter only checks directuseEffect calls. It won’t know about your Hook.

If you’re writing an Effect, start by using the React API directly:

functionChatRoom({roomId}){
const[serverUrl,setServerUrl] =useState('https://localhost:1234');

// ✅ Good: two raw Effects separated by purpose

useEffect(()=>{
constconnection =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:

functionChatRoom({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, whileuseImpressionLog(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:

Fork
import{useState,useEffect}from'react';exportfunctionuseOnlineStatus(){const[isOnline,setIsOnline] =useState(true);useEffect(()=>{functionhandleOnline(){setIsOnline(true);}functionhandleOffline(){setIsOnline(false);}window.addEventListener('online',handleOnline);window.addEventListener('offline',handleOffline);return()=>{window.removeEventListener('online',handleOnline);window.removeEventListener('offline',handleOffline);};},[]);returnisOnline;}

In the above example,useOnlineStatus is implemented with a pair ofuseState anduseEffect. 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 alreadytrue, but this may be wrong if the network already went offline. You can use the browsernavigator.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 calleduseSyncExternalStore which takes care of all of these problems for you. Here is youruseOnlineStatus Hook, rewritten to take advantage of this new API:

Fork
import{useSyncExternalStore}from'react';functionsubscribe(callback){window.addEventListener('online',callback);window.addEventListener('offline',callback);return()=>{window.removeEventListener('online',callback);window.removeEventListener('offline',callback);};}exportfunctionuseOnlineStatus(){returnuseSyncExternalStore(subscribe,()=>navigator.onLine,// How to get the value on the client()=>true// How to get the value on the server);}

Notice howyou didn’t need to change any of the components to make this migration:

functionStatusBar(){
constisOnline =useOnlineStatus();
// ...
}

functionSaveButton(){
constisOnline =useOnlineStatus();
// ...
}

This is another reason for why wrapping Effects in custom Hooks is often beneficial:

  1. You make the data flow to and from your Effects very explicit.
  2. You let your components focus on the intent rather than on the exact implementation of your Effects.
  3. When React adds new features, you can remove those Effects without changing any of your components.

Similar to adesign 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 Dive

Will React provide any built-in solution for data fetching?

Today, with theuse API, data can be read in render by passing aPromise touse:

import{use,Suspense}from"react";

functionMessage({messagePromise}){
constmessageContent =use(messagePromise);
return<p>Here is the message:{messageContent}</p>;
}

exportfunctionMessageContainer({messagePromise}){
return(
<Suspensefallback={<p>⌛Downloading message...</p>}>
<MessagemessagePromise={messagePromise}/>
</Suspense>
);
}

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';

functionShippingForm({country}){
constcities =use(fetch(`/api/cities?country=${country}`));
const[city,setCity] =useState(null);
constareas =city ?use(fetch(`/api/areas?city=${city}`)) :null;
// ...

If you use custom Hooks likeuseData 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 animationfrom scratch using the browserrequestAnimationFrame 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 youhold in a ref until it reaches1. Your code might start like this:

Fork
import{useState,useEffect,useRef}from'react';functionWelcome(){constref =useRef(null);useEffect(()=>{constduration =1000;constnode =ref.current;letstartTime =performance.now();letframeId =null;functiononFrame(now){consttimePassed =now -startTime;constprogress =Math.min(timePassed /duration,1);onProgress(progress);if(progress <1){// We still have more frames to paintframeId =requestAnimationFrame(onFrame);}}functiononProgress(progress){node.style.opacity =progress;}functionstart(){onProgress(0);startTime =performance.now();frameId =requestAnimationFrame(onFrame);}functionstop(){cancelAnimationFrame(frameId);startTime =null;frameId =null;}start();return()=>stop();},[]);return(<h1className="welcome"ref={ref}>      Welcome</h1>);}exportdefaultfunctionApp(){const[show,setShow] =useState(false);return(<><buttononClick={()=>setShow(!show)}>{show ?'Remove' :'Show'}</button><hr/>{show &&<Welcome/>}</>);}

To make the component more readable, you might extract the logic into auseFadeIn custom Hook:

Fork
import{useState,useEffect,useRef}from'react';import{useFadeIn}from'./useFadeIn.js';functionWelcome(){constref =useRef(null);useFadeIn(ref,1000);return(<h1className="welcome"ref={ref}>      Welcome</h1>);}exportdefaultfunctionApp(){const[show,setShow] =useState(false);return(<><buttononClick={()=>setShow(!show)}>{show ?'Remove' :'Show'}</button><hr/>{show &&<Welcome/>}</>);}

You could keep theuseFadeIn code as is, but you could also refactor it more. For example, you could extract the logic for setting up the animation loop out ofuseFadeIn into a customuseAnimationLoop Hook:

Fork
import{useState,useEffect}from'react';import{useEffectEvent}from'react';exportfunctionuseFadeIn(ref,duration){const[isRunning,setIsRunning] =useState(true);useAnimationLoop(isRunning,(timePassed)=>{constprogress =Math.min(timePassed /duration,1);ref.current.style.opacity =progress;if(progress ===1){setIsRunning(false);}});}functionuseAnimationLoop(isRunning,drawFrame){constonFrame =useEffectEvent(drawFrame);useEffect(()=>{if(!isRunning){return;}conststartTime =performance.now();letframeId =null;functiontick(now){consttimePassed =now -startTime;onFrame(timePassed);frameId =requestAnimationFrame(tick);}tick();return()=>cancelAnimationFrame(frameId);},[isRunning]);}

However, you didn’thave 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 JavaScriptclass:

Fork
import{useState,useEffect}from'react';import{FadeInAnimation}from'./animation.js';exportfunctionuseFadeIn(ref,duration){useEffect(()=>{constanimation =newFadeInAnimation(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 Hookscompletely like in the sandbox above. Then, the code you extractedbecomes 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 plainCSS Animation:

Fork
.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:fadeIn1000ms;}@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 withuse 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 likeuseMount. Keep their purpose specific.
  • It’s up to you how and where to choose the boundaries of your code.

Try out some challenges

Challenge 1 of 5:
Extract auseCounter 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 calleduseCounter. Your goal is to make theCounter component implementation look exactly like this:

exportdefaultfunctionCounter(){
constcount =useCounter();
return<h1>Seconds passed:{count}</h1>;
}

You’ll need to write your custom Hook inuseCounter.js and import it into theApp.js file.

Fork
import{useState,useEffect}from'react';exportdefaultfunctionCounter(){const[count,setCount] =useState(0);useEffect(()=>{constid =setInterval(()=>{setCount(c=>c +1);},1000);return()=>clearInterval(id);},[]);return<h1>Seconds passed:{count}</h1>;}


[8]ページ先頭

©2009-2025 Movatter.jp