📜 ⬆️ ⬇️

A comprehensive guide to useEffect

You have written several components using hooks . Perhaps - even created a small application. In general, the result suits you perfectly. You are accustomed to the API and in the process of work you discovered some unobvious useful techniques. You even created some of your own hooks and reduced your code by 300 lines, putting into them what was previously represented by repeating fragments of the program. What you did, you showed colleagues. “It worked out great,” they said about your project.


But sometimes, when you use useEffect , the component parts of software mechanisms do not fit particularly well with each other. It seems to you that you are missing something. All this seems to work with class-based component life cycle events ... but is it really?
Trying to understand what exactly does not suit you, you notice that you are asking yourself the following questions:


When I first started using hooks, I was also tormented by these questions. Even when I was preparing the documentation, I could not say that I am proficient in some subtleties. Since then, I had a few moments when I, having suddenly understood something important, wanted to exclaim: “Eureka!”. That I realized at these moments I want to tell you. What you learn now about useEffect will allow you to clearly see the obvious answers to the above questions.
')
But in order to see the answers to these questions, we first need to take a step back. The purpose of this article is not to give its readers some step-by-step instructions for working with useEffect . It aims to help you, as they say, to “ useEffect ” useEffect . And, frankly, there is not much to learn. In fact, most of the time we will spend on forgetting what we knew before.

In my head everything came together only after I stopped looking at the useEffect hook through the prism of familiar methods of life cycle components based on classes.

"You must forget what you were taught."


habr.com/ru/company/ruvds/blog/445276/Ioda


It is assumed that the reader of this material is to some extent familiar with the useEffect API. This is a fairly long article, it can be compared with a small book. The fact is that I prefer to express my thoughts that way. Below, very briefly, are the answers to the questions discussed above. Perhaps they are useful to those who do not have the time or desire to read all the material.

If the format in which we are going to consider useEffect , with all its explanations and examples, is not very suitable for you, you can wait a bit - until the moment when these explanations appear in countless other manuals. Here is the same story as with the React library itself, which in 2013 was something completely new. It takes some time for the development community to recognize a new mental model and for learning materials based on this model to appear.

Answers on questions


Here are brief answers to the questions posed at the beginning of this material, intended for those who do not want to read all this text. If, reading these answers, you feel that you do not really understand the meaning of what you read - look through the material. In the text you will find detailed explanations. If you are going to read everything - you can skip this section.

How do I play componentDidMount using useEffect?


Although the useEffect(fn, []) construct can be used to play the componentDidMount functional, it is not the exact equivalent of componentDidMount . Namely, it, unlike componentDidMount , captures properties and a state. Therefore, even inside the callback, you will see the original properties and state. If you want to see the latest version of something, you can write it in the ref link. But usually there is a simpler way to structure the code, so it is not necessary to do this. Remember that the mental effects model is different from the one that applies to componentDidMount and other component lifecycle methods. Therefore, trying to find exact equivalents may do more harm than good. In order to work productively, it is necessary, so to speak, to “think by effects”. The basis of their mental model is closer to the implementation of synchronization than to responding to component life cycle events.

How to properly load data inside useEffect? What []?


Here is a good guide to loading data using useEffect . Try to read it entirely! It is not as big as this. The brackets, [] , representing an empty array, mean that the effect does not use the values ​​involved in the React data stream, and for this reason, it can be considered safe to use it once. In addition, the use of an empty dependency array is a common source of error in the event that some value is actually used in the effect. You will need to master several strategies (mainly, presented in the form of useReducer and useCallback ), which can help eliminate the need for dependence rather than unjustifiably discarding this dependence.

â–ŤDo you need to specify functions as effect dependencies?


It is recommended to move functions beyond the limits of the components that do not need properties or in the state, and those functions that are used only by the effects are recommended to be placed inside the effects. If after this your effect still uses functions that are in the scope of the render (including functions from properties), wrap them in useCallback where they are declared, and try again to use them. Why is it important? Functions can "see" values ​​from properties and states, so they participate in the data stream. Here are more details about this in our FAQ.

â–ŤWhy does a program sometimes end up in an endless loop of reloading data?


This can occur when the data is loaded in an effect that does not have a second argument representing the dependencies. Without it, the effects are executed after each rendering operation - and this means that setting the state will result in the re-invoking of such effects. An infinite loop can occur even if a value is specified in the dependency array, which always changes. Find out - what is the value possible, removing the dependencies one by one. However, removing dependencies (or recklessly using [] ) is usually the wrong approach to solving a problem. Instead, it’s worth finding the source of the problem and really solving it. For example, functions may cause a similar problem. You can help solve it by placing them in effects, moving them outside the components, or wrapping them in useCallback . In order to avoid creating multiple objects, you can use useMemo .

â–ŤWhy does the old state sometimes appear inside the effects or do the old properties occur?


Effects always “see” the properties and state of the render in which they are declared. This helps to prevent errors , but in some cases may interfere with the normal operation of the component. In such cases, you can explicitly use mutable ref references to work with such values ​​(you can read about it at the end of the above article). If you think that you see properties or state from the old render, but do not expect it, then you may have missed some dependencies. To get used to seeing them, use this linter rule. After a couple of days, it will become something like your second nature. Also, take a look at this answer in our FAQ.

I hope these answers to questions turned out to be useful to those who read them. Now let's talk more about useEffect .

Each render has its own properties and state


Before we can discuss the effects, we need to talk about rendering.

Here is the functional counter component.

 function Counter() { const [count, setCount] = useState(0); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

Look carefully at the line <p>You clicked {count} times</p> . What does it mean? Does the count constant “observe” somehow for changes in the state and is it updated automatically? Such a conclusion can be considered something of a valuable first idea of ​​someone who studies React, but it is not an exact mental model of what is happening.

In our example, count is just a number. This is not some kind of magical "data binding", not some kind of "observer object" or "proxy", or anything else. Before us is a good old number, like this:

 const count = 42; // ... <p>You clicked {count} times</p> // ... 

At the time of the first component output, the count value obtained from useState() is 0. When we call setCount(1) , React calls the component again. This time the count will be 1. And so on:

 //     function Counter() { const count = 0; //  useState() // ... <p>You clicked {count} times</p> // ... } //       function Counter() { const count = 1; //  useState() // ... <p>You clicked {count} times</p> // ... } //        function Counter() { const count = 2; //  useState() // ... <p>You clicked {count} times</p> // ... } 

React calls a component whenever we update a state. As a result, each rendering operation “sees” the eigenvalue of the counter state, which, inside the function, is a constant.

As a result, this line does not perform any special data binding operation:

 <p>You clicked {count} times</p> 

It only embeds a numeric value in the code generated during rendering. This number is provided by React. When we call setCount , React again calls a component with a different count value. Then, React updates the DOM so that the document object model matches the most recent data that was rendered during component rendering.

The most important conclusion that can be made from this is that count is a constant within any particular render and does not change over time. The component that is called over and over is changing. Each render “sees” its own count value, which is isolated for each rendering operation.

In this article you can find details about this process.

Each render has its own event handlers.


So far, everything is clear. And what about event handlers?
Take a look at this example. Here, three seconds after pressing the button, a message box appears with information about the value stored in the count :

 function Counter() { const [count, setCount] = useState(0); function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>     <button onClick={handleAlertClick}>       Show alert     </button>   </div> ); } 

Suppose I perform the following sequence of actions:



Increasing the count value after clicking the Show alert button

What do you think will be displayed in the message box? Will 5 be displayed there, which corresponds to the count value at the moment the timer is triggered, or 3 - that is, the count value at the moment the button is pressed?

Now you will know the answer to this question, but if you want to find out everything yourself - here is the working version of this example.

If what you see seems incomprehensible to you, here is an example that is closer to reality. Imagine a chat application in which, in the state, the ID current recipient of the message is stored, and there is a Send button. In this material, what is happening is discussed in detail. In fact, the correct answer to the question of what appears in the message box is 3.

The mechanism for displaying the message box "captured" state at the moment of clicking the button.

There are ways to implement another variant of behavior, but for the time being we will deal with the standard behavior of the system. When building mental models of technology, it is important to distinguish the “path of least resistance” from all “emergency exits”.

How does all this work?

We have already said that the count value is a constant for each specific call to our function. I think it is worthwhile to dwell on this. The point is that our function is called many times (once for each rendering operation), but for each of these calls, the count inside it is a constant. This constant is set to some specific value (representing the state of a specific rendering operation).

This behavior of functions is not something special for React - regular functions behave in a similar way:

 function sayHi(person) { const name = person.name; setTimeout(() => {   alert('Hello, ' + name); }, 3000); } let someone = {name: 'Dan'}; sayHi(someone); someone = {name: 'Yuzhi'}; sayHi(someone); someone = {name: 'Dominic'}; sayHi(someone); 

In this example, the external variable someone reassigned several times. The same can happen somewhere inside React, the current state of the component may vary. However, inside the sayHi function there is a local constant name that is associated with a person from a particular call. This constant is local, so its values ​​in different function calls are isolated from each other! As a result, after a timeout, each displayed message box “remembers” its own name value.

This explains how our event handler captures the count value when it clicks a button. If we, working with components, apply the same principle, then it turns out that each render "sees" its own count value:

 //     function Counter() { const count = 0; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //       function Counter() { const count = 1; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //        function Counter() { const count = 2; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } 

As a result, each render, in fact, returns its own "version" of handleAlertClick . Each of these versions "remembers" its own count value:

 //     function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 0);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   0 // ... } //       function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 1);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   1 // ... } //        function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 2);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   2 // ... } 

That is why, in this example, event handlers "belong" to specific renders, and when you click a button, the component uses the count state from these renders.

Inside each particular renderer, the properties and state always remain the same. But if different properties of rendering use their own properties and state, the same happens with any mechanisms that use them (including event handlers). They also "belong" to specific renders. Therefore, even asynchronous functions inside event handlers will “see” the same count values.

It should be noted that in the above example, I embedded concrete count values ​​directly into the handleAlertClick functions. This “mental” substitution does not hurt us, since the constant count cannot change within a particular render. First, it is a constant, in the second - it is a number. It is safe to say that it is also possible to reflect on other values, such as objects, but only if we accept the rule not to make changes (mutations) of the state. In this case, we are satisfied with the call to setSomething(newObj) with a new object instead of changing the existing one, since with this approach the state belonging to the previous renderer is intact.

Each render has its own effects.


This material, as you know, is about effects, but we haven’t even talked about them yet. Now we fix it. As it turns out, working with effects is not particularly different from what we have already figured out.

Consider an example from the documentation, which is very similar to the one we have already reviewed:

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   document.title = `You clicked ${count} times`; }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

Now I have a question for you. How does the effect read the most recent count value?

Maybe, some “data binding” or “observer object” is used here, which updates the count value inside the effect function? Maybe count is a mutable variable, the value of which React sets inside our component, as a result of which the effect always sees its latest version?

Not.

We already know that in the rendering of a specific component, count is a constant. Even event handlers "see" the count value from the render to which they "belong" because count is a constant that is in a certain scope. The same is true for effects!

And it should be noted that this is not a variable count some way changing inside the "unchanged" effect. Before us is the effect function itself, which is different in each rendering operation.

Each version “sees” the count value from the render to which it “belongs”:

 //     function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${0} times`;   } ); // ... } //       function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${1} times`;   } ); // ... } //        function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${2} times`;   } ); // .. } 

React , DOM .

, ( ), , , , «» , «».

, , .

, ( , ). , , , .

, , :

React:


Component:


React:


:


React:


, . , , - :

Component:


React:


Component:


React:


:


React:


…


, , , , «» .

. Consider the following code:

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   setTimeout(() => {     console.log(`You clicked ${count} times`);   }, 3000); }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

, ?

, . , , , . But it is not so! , , , , , count . .




: «, ! ?».

, , this.setState , , . , , , , , :

   componentDidUpdate() {   setTimeout(() => {     console.log(`You clicked ${this.state.count} times`);   }, 3000); } 

, this.state.count count , , . , , , 5 , 5 .




, JavaScript-, , , , , setTimeout , . , (React this.state , ), .

— , , «» , . , , , . , , . , , , , , , .


, ( , , - API ) , .

:

 function Example(props) { useEffect(() => {   setTimeout(() => {     console.log(props.counter);   }, 1000); }); // ... } function Example(props) { const counter = props.counter; useEffect(() => {   setTimeout(() => {     console.log(counter);   }, 1000); }); // ... } 

, «» . ! . , .

, , - , , , , . , ref , .

, , , , , . , ( ), «» React-. , , . , .

, , , :

 function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => {   //        count   latestCount.current = count;   setTimeout(() => {     //            console.log(`You clicked ${latestCount.current} times`);   }, 3000); }); // ... 




- React . React this.state . , , latestCount.current . , . , , , .

?


, . , , «» .

:

 useEffect(() => {   ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);   return () => {     ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);   }; }); 

, props — {id: 10} , {id: 20} — . , :


( , , .)

, «» - , , «» - , . — , , , . .

React , . , . . . :


, «» props , {id: 10} , , props {id: 20} .

, …


— ?

: « ( , , - API ) , ».

! « » , . , , :

 //  ,  props  {id: 10} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(10, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(10, handleStatusChange);     };   } ); // ... } //  ,  props  {id: 20} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(20, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(20, handleStatusChange);     };   } ); // ... } 

, , … , «» , -, {id: 10} .

React . , , . props , .

,


React , . .

, :

 function Greeting({ name }) { return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

, <Greeting name="Dan" /> , — <Greeting name="Yuzhi" /> , <Greeting name="Yuzhi" /> . Hello, Yuzhi .

, , . React, . , , . $.addClass $.removeClass jQuery- ( — , «»), , CSS- React ( — , «»).

React DOM , . «» «».

. useEffect , React, .

 function Greeting({ name }) { useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

useEffect , , . , - , ! , «», «».

, A , B , — C , , C . (, - ), .

, , , . ( ).

How to deal with it?

React


React DOM. DOM , React DOM, - .

, :

 <h1 className="Greeting"> Hello, Dan </h1> 

:

 <h1 className="Greeting"> Hello, Yuzhi </h1> 

React :

 const oldProps = {className: 'Greeting', children: 'Hello, Dan'}; const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'}; 

React , children , DOM. , className . :

 domNode.innerText = 'Hello, Yuzhi'; // domNode.className    

- ? , , .

, , - :

 function Greeting({ name }) { const [counter, setCounter] = useState(0); useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}     <button onClick={() => setCounter(counter + 1)}>       Increment     </button>   </h1> ); } 

counter . document.title name , name . document.title counter , .

React … ?

 let oldEffect = () => { document.title = 'Hello, Dan'; }; let newEffect = () => { document.title = 'Hello, Dan'; }; //   React  ,        ? 

Not really. React , , . ( . name .)

, , ( deps ), useEffect :

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); //   

, React: «, , , , name ».

, , React :

 const oldEffect = () => { document.title = 'Hello, Dan'; }; const oldDeps = ['Dan']; const newEffect = () => { document.title = 'Hello, Dan'; }; const newDeps = ['Dan']; // React     ,     . //      ,     . 

, , ! - - .

React


React — . , , , , useEffect , , , . ( !)

 function SearchResults() { async function fetchData() {   // ... } useEffect(() => {   fetchData(); }, []); //   ?  .      . // ... } 

FAQ , . .

« !», — . : , , . , , , — , .

, , . , , , , . , . .

, , .

, React


, , React , .

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); 


—

, , , [] , , , , :

   useEffect(() => {   document.title = 'Hello, ' + name; }, []); // :    name 


—

. , «» , , .

, , , . , : « setInterval clearInterval ». . , , , useEffect , , , [] . - , ?

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); return <h1>{count}</h1>; } 

, , .

, « , », . , , , setInterval , . , ?

, — React , , . , count , React , , , . — .

count 0. setCount(count + 1) setCount(0 + 1) . , — [] , setCount(0 + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); //  setCount(1)     }, 1000);     return () => clearInterval(id);   },   [] //    ); // ... } //       1 function Counter() { // ... useEffect(   //     - ,    //   React  ,   .   () => {     const id = setInterval(() => {       setCount(1 + 1);     }, 1000);     return () => clearInterval(id);   },   [] ); // ... } 

React, , , — .

count — , ( ):

   const count = // ... useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); 

. React .


,

. , , React , , . — - .


React , . , , , .

, , , . count :

 useEffect(() => { const id = setInterval(() => {   setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]); 

. , , — , . count , count , setCount(count + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [0] // [count] ); // ... } //  ,   1 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(1 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [1] // [count] ); // ... } 

, setInterval , count , . , .


,

, , , . — , .

.


, count .

 useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, [count]); 

, , count . , count setCount . , , count . , , setState :

   useEffect(() => {   const id = setInterval(() => {     setCount(c => c + 1);   }, 1000);   return () => clearInterval(id); }, []); 

« ». , count - , setCount(count + 1) . count - , count + 1 «» React. React count . , React — , , , .

setCount(c => c + 1) . « React », , . « » , , .

, , , . React. count :


,

.

, setInterval , , c => c + 1 . count . React .

Google Docs


, , — ? , , «», , . , Google Docs . . , .

, . . , setCount(c => c + 1) , , setCount(count + 1) , «» count . , ( — «»). « React» — . .

( ) , Google Docs . — , React . , , ( , , ) .

, setCount(c => c + 1) , . , . , , , , , . setCount(c => c + 1) . useReducer .


, : count step . setInterval , step :

 function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => {   const id = setInterval(() => {     setCount(c => c + step);   }, 1000);   return () => clearInterval(id); }, [step]); return (   <>     <h1>{count}</h1>     <input value={step} onChange={e => setStep(Number(e.target.value))} />   </> ); } 

.

, React . step , . .

: step setInterval — step . , , , ! , , , , , .

, , , setInterval , step . step ?

, , useReducer .

, setSomething(something => ...) , , . «», , , .

step dispatch :

 const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state; useEffect(() => { const id = setInterval(() => {   dispatch({ type: 'tick' }); //  setCount(c => c + step); }, 1000); return () => clearInterval(id); }, [dispatch]); 

.

: « , ?». , React , dispatch . .

!

( dispatch setstate useRef , React , . — .)

, , , , . step . , . , . :

 const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') {   return { count: count + step, step }; } else if (action.type === 'step') {   return { count, step: action.step }; } else {   throw new Error(); } } 

, , , .

useReducer — -


, , , . , , ? , , API <Counter step={1} /> . , props.step ?

, ! , :

 function Counter({ step }) { const [count, dispatch] = useReducer(reducer, 0); function reducer(state, action) {   if (action.type === 'tick') {     return state + step;   } else {     throw new Error();   } } useEffect(() => {   const id = setInterval(() => {     dispatch({ type: 'tick' });   }, 1000);   return () => clearInterval(id); }, [dispatch]); return <h1>{count}</h1>; } 

, . , , , , . .

dispatch . , , . .

, , . «» , , ? , dispatch , React . . .

useReducer «-» . , . , , , , .


, - , .

, , , :

 function SearchResults() { const [data, setData] = useState({ hits: [] }); async function fetchData() {   const result = await axios(     'https://hn.algolia.com/api/v1/search?query=react',   );   setData(result.data); } useEffect(() => {   fetchData(); }, []); //   ? // ... 

, .

, , . , , , , , , , , .

, , , , :

 function SearchResults() { // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=react'; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, , :

 function SearchResults() { const [query, setQuery] = useState('react'); // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, (, ), . .

, . , :

 function SearchResults() { // ... useEffect(() => {   //      !   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=react';   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, []); //    . // ... } 

.

? , « ». React, - .

getFetchUrl , query , , , , . — , query :

 function SearchResults() { const [query, setQuery] = useState('react'); useEffect(() => {   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=' + query;   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, [query]); //    . // ... } 

.

, « React». query . , , , , . , , .

exhaustive-deps eslint-plugin-react-hooks , . , , .




It is very convenient.

, ?


. , , . , , .

? , . : React . . , « ». , , . , , , !

, , . , getFetchUrl :

 function SearchResults() { function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //  : getFetchUrl useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //  : getFetchUrl // ... } 

getFetchUrl — , .

, «» , . getFetchUrl (, , ), :

 function SearchResults() { //       function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -    ... }, [getFetchUrl]); //   ,     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //   ,     . // ... } 

, getFetchUrl . , — . - , , . , , , .

— .

, , :

 //        function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } function SearchResults() { useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //     . // ... } 

, . , , .

. , useCallback :

 function SearchResults() { //    ,   const getFetchUrl = useCallback((query) => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []);  //      . useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, [getFetchUrl]); //      . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //       // ... } 

useCallback . : , -, , , .

, . ( 'react' 'redux' ). , , , query . , , query , getFetchUrl .

, query useCallback :

 function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = useCallback(() => { //   query   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []); //  : query // ... } 

useCallback query , , getFetchUrl , query :

 function SearchResults() { const [query, setQuery] = useState('react'); //      query const getFetchUrl = useCallback(() => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, [query]);  //    . useEffect(() => {   const url = getFetchUrl();   // ...    -   ... }, [getFetchUrl]); //    . // ... } 

useCallback , query , getFetchUrl , , . query , getFetchUrl , . Excel: - , , , .

— , . , :

 function Parent() { const [query, setQuery] = useState('react'); //      query const fetchData = useCallback(() => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + query;   // ...      ... }, [query]);  //       return <Child fetchData={fetchData} /> } function Child({ fetchData }) { let [data, setData] = useState(null); useEffect(() => {   fetchData().then(setData); }, [fetchData]); //       // ... } 

fetchData Parent query , Child , .

?


, , , , . , , , , :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -   ... }; render() {   return <Child fetchData={this.fetchData} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } render() {   // ... } } 

, : « , , , useEffect — componentDidMount componentDidUpdate . !». componentDidUpdate :

 class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   //         if (this.props.fetchData !== prevProps.fetchData) {     this.props.fetchData();   } } render() {   // ... } } 

, fetchData — ! (, , , .) - , . this.props.fetchData prevProps.fetchData . , , ?

   componentDidUpdate(prevProps) {   this.props.fetchData(); } 

. . ( .) , fetchData this.state.query ?

   render() {   return <Child fetchData={this.fetchData.bind(this, this.state.query)} />; } 

this.props.fetchData !== prevProps.fetchData true , , query ! .

, , , query Child . , , query , query :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -    ... }; render() {   return <Child fetchData={this.fetchData} query={this.state.query} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   if (this.props.query !== prevProps.query) {     this.props.fetchData();   } } render() {   // ... } } 

, , - , , .

, , . this , . , , , , - . , this.props.fetchData , , , , , .

- useCallback . , , , . , . useCallback props.fetchData .

, useMemo :

 function ColorPicker() { //         Child, //       . const [color, setColor] = useState('pink'); const style = useMemo(() => ({ color }), [color]); return <Child style={style} />; } 

, useCallback , - . « », , , . , . , .

, fetchData ( ), . , , . (« props.onComplete , ?») , .


, :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . . — , :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } componentDidUpdate(prevProps) {   if (prevProps.id !== this.props.id) {     this.fetchData(this.props.id);   } } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . , . , {id: 10} , {id: 20} , , . , , , . And this is wrong.

, , . — , , async/await ( , - ) , ( , ).

, async -. (, , , , .)

, , ! .

, :

 function Article({ id }) { const [article, setArticle] = useState(null); useEffect(() => {   let didCancel = false;   async function fetchData() {     const article = await API.fetchArticle(id);     if (!didCancel) {       setArticle(article);     }   }   fetchData();   return () => {     didCancel = true;   }; }, [id]); // ... } 

, , , . , .


, , , , , . , , , . . — .

useEffect , , , . React. , useEffect .

, , « », . . , , , , «» , .

, useEffect , . — . — , , — , . , , , , API.

, , useFetch , , useTheme , . , , useEffect . , , , .

, , useEffect . — , . , . ?

Suspense React , , - ( : , , ) .

Suspense , , useEffect , , , - . , , , . , , , will be a good way to reuse the logic responsible for loading data.

Results


Now you know about the effects of almost everything that I know. And if you, starting to read this material and reviewing the section with answers to questions, are faced with something incomprehensible, now, I hope, everything fell into place.

Source: https://habr.com/ru/post/445276/


All Articles