Best practice to prevent state update warning for unmounted component from a handler - reactjs

It is a common use-case to fetch and display the data from an external API (by using XHR requests) when a certain UI component (e.g. a <button />) is clicked. However, if the component was unmounted in the meantime, the following warning appears in the console:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
In fact, the most common solution (approved by #dan-abramov) to avoid the warning seems to keep track of the mount state of the component by using the return function of useEffect to cleanup.
import React, { useState, useRef, useEffect } from "react";
import axios from "axios";
export default function PhotoList() {
const mounted = useRef(true);
const [photos, setPhotos] = useState(null);
useEffect(() => {
return () => {
mounted.current = false;
};
}, []);
function handleLoadPhotos() {
axios("https://jsonplaceholder.typicode.com/photos").then(res => {
if (mounted.current) {
setPhotos(res.data);
}
});
}
return (
<div>
<button onClick={handleLoadPhotos}>Load photos</button>
{photos && <p>Loaded {photos.length} photos</p>}
</div>
);
}
However, this seems to cause unnecessary overhead to keep track of the mounting state and to check it before every state update. This becomes especially obvious when Observables (where you can unsubscribe) instead of Promises are used.
While you indeed can unsubscribe inside of the useEffect using the cleanup function in a very neat way:
useEffect(() => {
// getPhotos() returns an observable of the photo list
const photos$ = getPhotos().subscribe(setPhotos);
return () => photos$.unsubscribe();
}, []);
The same smart cleanup is not possible within a handler:
function handleLoadPhotos() {
const photos$ = getPhotos().subscribe(setPhotos);
// how to unsubscribe on unmounting?
}
Is there a best practice to avoid the warning without the ugly manual tracking of the mounting state with useRef()? Are there good approaches for that when using Observables?

Problem is that you are trying to fetch data in your component. This is not a good idea since the component could be unmounted and you would face many possible errors.
So that, you should look for other ways.
I always do async operations in redux thunks.
You should avoid your approach. Use redux and redux-thunk if you like. If not, try to find another solution to move async operations outside of your components.
In fact, you should be writing declarative ui components which renders for given props. So that, your data should be outside of your components logic too.

That's an awesome question! This is how I would do it:
First, define a helper function (it's not cheating because it really is a highly reusable function whenever you're dealing with React and observables combined):
import * as React from 'react';
import { Observable } from 'rxjs';
export const useObservable = <Value>(
arg: () => {
observable: Observable<Value>;
value: Value;
},
) => {
const { observable, value } = React.useMemo(arg, []);
const [state, setState] = React.useState<Value>(value);
React.useEffect(() => {
const subscription = observable.subscribe(value => setState(value));
return () => subscription.unsubscribe();
}, []);
return state;
};
Just to help illustrate what this function does, the following component will display the latest value emitted by myObservable:
() => {
const value = useObservable(() => ({
observable: myObservable,
value: 'Nothing emitted yet',
}));
return <span>{value}</span>;
};
Your component will then look like this:
export default function PhotoList() {
const clicksSubject = React.useMemo(() => new Subject<undefined>(), []);
const photos = useObservable(() => ({
observable: clicksSubject.pipe(
switchMap(() => axiosApiCallReturningAnObservable()),
map(res => res.data),
),
value: null,
}));
return (
<div>
<button
onClick={() => {
clicksSubject.next(undefined);
}}
>
Load photos
</button>
{photos && <p>Loaded {photos.length} photos</p>}
</div>
);
}
When the component is dismounted, useObservable unsubs from the observable that was passed to it. This makes sure that we don't at a later point attempt to set the state, and that the data fetching API aborts (or at least gets a chance to abort) the HTTP request.

Related

Unexpected behaviour of setInterval function (interval keeps on decreasing) [duplicate]

Are there ways to simulate componentDidMount in React functional components via hooks?
For the stable version of hooks (React Version 16.8.0+)
For componentDidMount
useEffect(() => {
// Your code here
}, []);
For componentDidUpdate
useEffect(() => {
// Your code here
}, [yourDependency]);
For componentWillUnmount
useEffect(() => {
// componentWillUnmount
return () => {
// Your code here
}
}, [yourDependency]);
So in this situation, you need to pass your dependency into this array. Let's assume you have a state like this
const [count, setCount] = useState(0);
And whenever count increases you want to re-render your function component. Then your useEffect should look like this
useEffect(() => {
// <div>{count}</div>
}, [count]);
This way whenever your count updates your component will re-render. Hopefully this will help a bit.
There is no exact equivalent for componentDidMount in react hooks.
In my experience, react hooks requires a different mindset when developing it and generally speaking you should not compare it to the class methods like componentDidMount.
With that said, there are ways in which you can use hooks to produce a similar effect to componentDidMount.
Solution 1:
useEffect(() => {
console.log("I have been mounted")
}, [])
Solution 2:
const num = 5
useEffect(() => {
console.log("I will only run if my deps change: ", num)
}, [num])
Solution 3 (With function):
useEffect(() => {
const someFunc = () => {
console.log("Function being run after/on mount")
}
someFunc()
}, [])
Solution 4 (useCallback):
const msg = "some message"
const myFunc = useCallback(() => {
console.log(msg)
}, [msg])
useEffect(() => {
myFunc()
}, [myFunc])
Solution 5 (Getting creative):
export default function useDidMountHook(callback) {
const didMount = useRef(null)
useEffect(() => {
if (callback && !didMount.current) {
didMount.current = true
callback()
}
})
}
It is worth noting that solution 5 should only really be used if none of the other solutions work for your use case. If you do decide you need solution 5 then I recommend using this pre-made hook use-did-mount.
Source (With more detail): Using componentDidMount in react hooks
There's no componentDidMount on functional components, but React Hooks provide a way you can emulate the behavior by using the useEffect hook.
Pass an empty array as the second argument to useEffect() to run only the callback on mount only.
Please read the documentation on useEffect.
function ComponentDidMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount');
}, []);
return (
<div>
<p>componentDidMount: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<div>
<ComponentDidMount />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
useEffect() hook allows us to achieve the functionality of componentDidMount, componentDidUpdate componentWillUnMount functionalities.
Different syntaxes of useEffect() allows to achieve each of the above methods.
i) componentDidMount
useEffect(() => {
//code here
}, []);
ii) componentDidUpdate
useEffect(() => {
//code here
}, [x,y,z]);
//where x,y,z are state variables on whose update, this method should get triggered
iii) componentDidUnmount
useEffect(() => {
//code here
return function() {
//code to be run during unmount phase
}
}, []);
You can check the official react site for more info. Official React Page on Hooks
Although accepted answer works, it is not recommended. When you have more than one state and you use it with useEffect, it will give you warning about adding it to dependency array or not using it at all.
It sometimes causes the problem which might give you unpredictable output. So I suggest that you take a little effort to rewrite your function as class. There are very little changes, and you can have some components as class and some as function. You're not obligated to use only one convention.
Take this for example
function App() {
const [appointments, setAppointments] = useState([]);
const [aptId, setAptId] = useState(1);
useEffect(() => {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = aptId;
console.log(aptId);
setAptId(aptId + 1);
return item;
})
setAppointments(apts);
});
}, []);
return(...);
}
and
class App extends Component {
constructor() {
super();
this.state = {
appointments: [],
aptId: 1,
}
}
componentDidMount() {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = this.state.aptId;
this.setState({aptId: this.state.aptId + 1});
console.log(this.state.aptId);
return item;
});
this.setState({appointments: apts});
});
}
render(...);
}
This is only for example. so lets not talk about best practices or potential issues with the code. Both of this has same logic but the later only works as expected. You might get componentDidMount functionality with useEffect running for this time, but as your app grows, there are chances that you MAY face some issues. So, rather than rewriting at that phase, it's better to do this at early stage.
Besides, OOP is not that bad, if Procedure-Oriented Programming was enough, we would never have had Object-Oriented Programming. It's painful sometimes, but better (technically. personal issues aside).
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Please visit this official docs. Very easy to understand the latest way.
https://reactjs.org/docs/hooks-effect.html
Info about async functions inside the hook:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
useLayoutEffect hook is the best alternative to ComponentDidMount in React Hooks.
useLayoutEffect hook executes before Rendering UI and useEffect hook executes after rendering UI. Use it depend on your needs.
Sample Code:
import { useLayoutEffect, useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("useEffect Statements");
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect Statements");
}, []);
return (
<div>
<h1>Hello Guys</h1>
</div>
);
}
Yes, there is a way to SIMULATE a componentDidMount in a React functional component
DISCLAIMER: The real problem here is that you need to change from "component life cycle mindset" to a "mindset of useEffect"
A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it's still a funtion like for example:
const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)
mySecondFunction()
myFunction()
/* Result:
'b'
'a'
*/
That is really simple isn't it?
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
And in this specific case it's true. But what happens if I do something like that:
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
This "cleverFunction" we are defining it's not the same in every re-render of the component.
This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.
The real problem with that is that a React functional component is a function that "executes itself" several times depending on your state thanks to the useEffect hook (among others).
In short useEffect it's a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it's the array dependencies.
Leaving that undefined leaves you open to hard-to-find bugs.
Because of that it's important to know how this work, and what you can do to get what you want in the "react" way.
const initialState = {
count: 0,
step: 1,
done: false
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'doSomething') {
if(state.done === true) return state;
return { ...state, count: state.count + state.step, state.done:true };
} else if (action.type === 'step') {
return { ...state, step: action.step };
} else {
throw new Error();
}
}
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
dispatch({ type: 'doSomething' });
}, [dispatch]);
return (
<div>
<h1>Hi!</h1>
</div>
)}
useReducer's dispatch method it's static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of do it right.
Source: The Complete Guide to useEffect - By Dan Abramov
That being said if you like to experiment with things and want to know how to do it "the imperative wat" you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it's recommended to avoid it if you're not familiar with what happen with react behind curtains.
That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it's not the focus of the problem here, you can read this amazing article about useRef ). So it's the best approach to known when the first render of the component happened.
I leave an example showing 3 different ways of synchronise an "outside" effect (like an external function) with the "inner" component state.
You can run this snippet right here to see the logs and understand when these 3 functions are executed.
const { useRef, useState, useEffect, useCallback } = React
// External functions outside react component (like a data fetch)
function renderOnce(count) {
console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}
function renderOnFirstReRender(count) {
console.log(`renderOnUpdate: I executed just ${count} times!`);
}
function renderOnEveryUpdate(count) {
console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}
const MyComponent = () => {
const [count, setCount] = useState(undefined);
const mounted = useRef(0);
// useCallback is used just to avoid warnings in console.log
const renderOnEveryUpdateCallBack = useCallback(count => {
renderOnEveryUpdate(count);
}, []);
if (mounted.current === 0) {
renderOnce(count);
}
if (mounted.current === 1) renderOnFirstReRender(count);
useEffect(() => {
mounted.current = mounted.current + 1;
renderOnEveryUpdateCallBack(count);
}, [count, renderOnEveryUpdateCallBack]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
</div>
);
};
class App extends React.Component {
render() {
return (
<div>
<h1>hI!</h1>
</div>
);
}
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you execute it you will see something like this:
You want to use useEffect(), which, depending on how you use the function, can act just like componentDidMount().
Eg. you could use a custom loaded state property which is initially set to false, and switch it to true on render, and only fire the effect when this value changes.
Documentation
the exact equivalent hook for componentDidMount() is
useEffect(()=>{},[]);
hope this helpful :)

How to call a hook function into a map in react

I'm using ketting for my React REST client.
The library provides hooks to access some data, In my case, this is this hook :
import { useResource } from 'react-ketting';
...
const { loading, error, data } = useResource('https://api.example/article/5');
I have multiples resources and I want to loop on them:
items.map(item => {
const { loading, error, data, resourceState } = useResource(item);
myItems.push({
title: data.name,
onClick: () => go(resourceState.links.get('self').href),
});
});
But React doesn't accept looping the useResource hook like this.
So I found a dirty solution that I'm not proud of...
import React from 'react';
import { useCollection, useResource } from 'react-ketting';
let myItems = [];
const Collection = ({ resource, go }) => {
const { items } = useCollection(resource);
myItems = [];
return (
<div>
{items.map(item => (
<CollectionItem go={go} resource={item} />
))}
<ElementsUser elements={myItems} />
</div>
);
};
const CollectionItem = ({ resource, go }) => {
const { data, resourceState } = useResource(resource);
myItems.push({
title: data.name,
onClick: () => go(resourceState.links.get('self').href),
});
return null;
};
Do you have any tips to deal with that problem?
And is it possible that it causes a :
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Using 1 component per Resource is exactly the pattern that's recommended. I'd even say it's a best practice. Note that a call to useResource will not result in an API call if there was a cached resource state.
If you find that you are seeing 1 request per iteration, you might want to make sure that your collection returns every member as an embedded resource. If you use the useCollection hook, the request will include a Prefer: transclude=item header to give a hint to the server that embedding might be desired.
Also, react-ketting will do cleanups on unmount.

Prevent updating an unmounted component during async event handler

I have an async event handler that deletes a component, but this component is using state to watch the event handler execution status. The event handler is a mock of deleting an item from a remote database.
The problem is that upon successful deletion, the component is unmounted, so the final state update (to indicate that deletion is done) triggers the error "Can't perform a React state update on an unmounted component".
I understand that it is frequent classical issue, I would like to know what is the best way to solve it.
A sandbox:
The full code for reference:
import React from "react";
export default function App() {
const [fruits, setFruits] = React.useState(["apple", "pear", "peach"]);
return (
<ul>
{fruits.map((fruit) => (
<Row key={fruit} fruit={fruit} setFruits={setFruits} />
))}
</ul>
);
}
function Row({ fruit, setFruits }) {
const [isDeleting, setIsDeleting] = React.useState(false);
const handleDelete = async () => {
setIsDeleting(true);
try {
await deleteFruit(fruit, setFruits);
} catch (error) {
console.log("An error occured");
}
setIsDeleting(false);
};
return (
<li>
{fruit}
<button onClick={handleDelete} disabled={isDeleting}>
X
</button>
</li>
);
}
async function deleteFruit(fruitToDelete, setFruits) {
// mock remote DB
return new Promise((resolve) => {
setTimeout(() => {
setFruits((fruits) => fruits.filter((f) => f !== fruitToDelete));
resolve();
}, 1000);
});
}
I have tried to prevent the issue by recording if the component is mounted with useRef and useEffect. It works, but I find that it is not easily readable. Is there a more explicit method to achieve this behaviour?
In component Row's render function:
const refIsMounted = React.useRef(true);
React.useEffect(() => {
refIsMounted.current = true;
return () => {
refIsMounted.current = false;
};
}, []);
And the async event handler:
if (refIsMounted.current) {
setIsDeleting(false);
}
based on useEffect documents you can return a clean-up function.
Often, effects create resources that need to be cleaned up before the
component leaves the screen, such as a subscription or timer ID. To do
this, the function passed to useEffect may return a clean-up function.
so your code will be like below:
useEffect(()=>{
return ()=>{
setIsDeleting(false);}
},[])
I realized what was the issue: the lifetime of a component that triggers an async event handler is independent of the execution time of this async event handler
So the solution is either:
to put the state modified by the handler in a component which we know will outlive the handler, either higher in the component hierarchy or in Redux.
to use the useRef trick as described above to check whether the triggering component is still in existence
Here I lifted the state in the parent component:

What is the best way to use redux action in useEffect?

I have a React Component like shown bellow (some parts are ommited) and I'm using redux for state management. The getRecentSheets action contains an AJAX request and dispatches the response to redux which updates state.sheets.recentSheets with the response's data.
All this works as expected, but on building it throws warning about useEffect has a missing dependency: 'getRecentSheets'. But if I add getRecentSheets to useEffect's dependency array it starts to rerun indefinitely and thus freezes the app.
I've read React documentation about the useEffect hook https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies but it doesn't provide a good example for such usecase. I suppose it is something with useCallback or react-redux useDispatch, but without examples I'm not sure how to implement it.
Can someone please tell me what the most concise and idiomatic way to use redux action in useEffect would be and how to avoid warnings about missing dependencies?
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import SheetList from '../components/sheets/SheetList';
import { getRecentSheets } from '../store/actions/sheetsActions';
const mapStateToProps = (state) => {
return {
recentSheets: state.sheets.recentSheets,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getRecentSheets: () => dispatch(getRecentSheets()),
}
}
const Home = (props) => {
const {recentSheets, getRecentSheets} = props;
useEffect(() => {
getRecentSheets();
}, [])
return <SheetList sheets={ recentSheets } />
};
export default connect(mapStateToProps, mapDispatchToProps) (Home);
After all, it seems that correct way will be as follows:
// ...
import { useDispatch } from 'react-redux';
import { getRecentSheets } from '../store/actions/sheetsActions';
const Home = props => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getRecentSheets());
}, [dispatch])
// ...
};
This way it doesn't complain about getRecentSheets missing in dependencies array. As I understood from reading React doc on hooks that's because it's not defined inside the component. Though I'm new to frontend and I hope I didn't mess something up here.
Passing an empty array in your hook tells React your hook function will not have any dependent values from either props or state.
useEffect(() => {
getRecentSheets();
}, [])
The infinite loop arises when you declare the dispatcher as a dependency on the hook. When the component is initialized, props.recentSheets hasn't been set, and will rerender once you make your AJAX call.
useEffect(() => {
getRecentSheets();
}, [getRecentSheets])
You could try something like this:
const Home = ({recentSheets}) => {
const getRecentSheetsCallback = useCallback(() => {
getRecentSheets();
})
useEffect(() => {
getRecentSheetsCallback();
}, [recentSheets]) // We only run this effect again if recentSheets changes
return <SheetList sheets={ recentSheets } />
};
No matter how many times Homes re-renders, you retain the memoized function to your dispatch call.
Alternatively, you may have encountered find similar patterns utilizing local state and then make your effect "depend" on sheets.
const [sheets, setSheets] = useState(recentSheets)
Hope this helps
I would add a check to see if recentSheets exists or not, using that as my dependency.
useEffect(() => {
if (!recentSheets) getRecentSheets();
}, [recentSheets])

Authentication listeners when refactoring to React hooks

I'm having a little trouble figuring out how to change my authentication handling component when refactoring from a React class to React hooks.
Here's the relavant code in my class:
state = {
user: null
}
componentDidMount() {
authGetUser(user => {
if (user !== this.state.user) {
this.setState({user})
}
})
}
componentWillUnmount() {
authUnsubscribe()
}
handleAuthClick = () => {
if (this.state.user) {
authSignOut()
} else {
authSignIn()
}
}
And here it is with hooks:
const [user, setUser] = useState<firebase.User | null>(null)
useEffect(() => {
return authUnsubscribe() // runs on mount and unmount only
}, [])
useEffect(() => {
authGetUser(usr => setUser(usr))
}, [])
const handleAuthClick = () => {
if (user) {
authSignOut()
} else {
authSignIn()
}
}
Also, here are my other relevant methods:
const authGetUser = (callback: (user: firebase.User | null) => void) => {
initFirebase()
authUnsubscribe()
userUnsubscribe = firebaseAuth.onAuthStateChanged(callback)
}
export const authUnsubscribe = () => {
if (userUnsubscribe) {
userUnsubscribe()
}
}
const authSignIn = () => {
googleAuth.signIn().then((googleUser: any) => {
var credential = firebase.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token)
firebaseAuth.signInAndRetrieveDataWithCredential(credential)
})
}
const authSignOut = () => {
googleAuth
.signOut()
.then(firebaseAuth.signOut())
}
Both examples work. However, when I log out and log in with the hooks version, I get an error message in console saying
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
which suggests that the cleanup is not done properly.
Yes, I know I could just continue using the version with the class which works. But I want to understand React hooks better by solving this.
Any ideas?
Wouldn't this works for you? You could use a single useEffect().
React Hooks API DOCs
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
setUser(null); // <--- TRY DOING SOMETHING LIKE THIS
subscription.unsubscribe();
};
},
[],
);
The clean-up function runs before the component is removed from the UI
to prevent memory leaks. Additionally, if a component renders multiple
times (as they typically do), the previous effect is cleaned up before
executing the next effect. In our example, this means a new
subscription is created on every update. To avoid firing an effect on
every update, refer to the next section.
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
Generally, this happens when we have asynchronous requests and the component is unmounted before, occurring memory leak. Obviously, that this not occur in class-based components because we have componentDidMount() and componentWillUnmount() hooks, so it's more confident than useEffect() that we have manipulated the state, so I think that you need to identify the reason for the application unmount and there is the solution.
You should use one useEffect() instead two like this:
useEffect(() => {
authGetUser(usr => setUser(usr))
return authUnsubscribe() // runs on mount and unmount only
}, [])

Resources