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? componentDidMount
using useEffect
?useEffect
? What is []
?useEffect
will allow you to clearly see the obvious answers to the above questions.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.useEffect
hook through the prism of familiar methods of life cycle components based on classes.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.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.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.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.[]
) 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
.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.useEffect
. function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
<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.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> // ...
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> // ... }
counter
state, which, inside the function, is a constant. <p>You clicked {count} times</p>
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.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.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> ); }
count
to 3 by clicking the Click me
button.Show alert
button.count
value at the moment the timer is triggered, or 3 - that is, the count
value at the moment the button is pressed?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.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). 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);
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.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); } // ... }
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 // ... }
count
state from these renders.count
values.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. 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> ); }
count
value?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?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!count
some way changing inside the "unchanged" effect. Before us is the effect function itself, which is different in each rendering operation.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`; } ); // .. }
<p>You clicked 0 times</p>
.() => { document.title = 'You clicked 0 times' }
.() => { document.title = 'You clicked 0 times' }
.<p>You clicked 1 times</p>
.() => { document.title = 'You clicked 1 times' }
.() => { document.title = 'You clicked 1 times' }
. 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> ); }
count
. .this.setState
, , . , , , , , : componentDidUpdate() { setTimeout(() => { console.log(`You clicked ${this.state.count} times`); }, 3000); }
this.state.count
count
, , . , , , 5 , 5 .setTimeout
, . , (React this.state
, ), . function Example(props) { useEffect(() => { setTimeout(() => { console.log(props.counter); }, 1000); }); // ... } function Example(props) { const counter = props.counter; useEffect(() => { setTimeout(() => { console.log(counter); }, 1000); }); // ... }
ref
, . 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); }); // ...
this.state
. , , latestCount.current
. , . , , , . useEffect(() => { ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange); }; });
props
— {id: 10}
, {id: 20}
— . , :{id: 10}
.{id: 20}
.{id: 20}
.{id: 20}
.{id: 20}
.{id: 10}
.{id: 20}
.props
, {id: 10}
, , props
{id: 20}
. // , 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}
.props
, . function Greeting({ name }) { return ( <h1 className="Greeting"> Hello, {name} </h1> ); }
<Greeting name="Dan" />
, — <Greeting name="Yuzhi" />
, <Greeting name="Yuzhi" />
. Hello, Yuzhi
.$.addClass
$.removeClass
jQuery- ( — , «»), , CSS- React ( — , «»).useEffect
, React, . function Greeting({ name }) { useEffect(() => { document.title = 'Hello, ' + name; }); return ( <h1 className="Greeting"> Hello, {name} </h1> ); }
useEffect
, , . , - , ! , «», «».A
, B
, — C
, , C
. (, - ), . <h1 className="Greeting"> Hello, Dan </h1>
<h1 className="Greeting"> Hello, Yuzhi </h1>
const oldProps = {className: 'Greeting', children: 'Hello, Dan'}; const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'};
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
, . let oldEffect = () => { document.title = 'Hello, Dan'; }; let newEffect = () => { document.title = 'Hello, Dan'; }; // React , ?
name
.)deps
), useEffect
: useEffect(() => { document.title = 'Hello, ' + name; }, [name]); //
name
». const oldEffect = () => { document.title = 'Hello, Dan'; }; const oldDeps = ['Dan']; const newEffect = () => { document.title = 'Hello, Dan'; }; const newDeps = ['Dan']; // React , . // , .
useEffect
, , , . ( !) function SearchResults() { async function fetchData() { // ... } useEffect(() => { fetchData(); }, []); // ? . . // ... }
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
, . , ?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); }, [] ); // ... }
count
— , ( ): const count = // ... useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, []);
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 », , . « » , , .count
:setInterval
, , c => c + 1
. count
. React .setCount(c => c + 1)
, , setCount(count + 1)
, «» count
. , ( — «»). « 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))} /> </> ); }
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]);
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(); } }
<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(); }, []); // . // ... }
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]); // . // ... }
query
. , , , , . , , .exhaustive-deps
eslint-plugin-react-hooks
, . , , .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'); // ... - ... }, []); // . // ... }
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
, , useEffect
, , , - . , , , . , , , will be a good way to reuse the logic responsible for loading data.Source: https://habr.com/ru/post/445276/
All Articles