How to prevent state updates from a function, running an async call - javascript

so i have a bit of a weird problem i dont know how to solve.
In my code i have a custom hook with a bunch of functionality for a fetching a list
of train journeys. I have some useEffects to that keeps loading in new journeys untill the last journey of the day.
When i change route, while it is still loading in new journeys. I get the "changes to unmounted component" React error.
I understand that i get this error because the component is doing an async fetch that finishes after i've gone to a new page.
The problem i can't figure out is HOW do i prevent it from doing that? the "unmounted" error always occur on one of the 4 lines listed in the code snippet.
Mock of the code:
const [loading, setLoading] = useState(true);
const [journeys, setJourneys] = useState([]);
const [hasLaterDepartures, setHasLaterDepartures] = useState(true);
const getJourneys = async (date, journeys) => {
setLoading(true);
setHasLaterDepartures(true);
const selectedDateJourneys = await fetchJourney(date); // Fetch that returns 0-3 journeys
if (condition1) setHasLaterDepartures(false); // trying to update unmounted component
if (condition2) {
if (condition3) {
setJourneys(something1); // trying to update unmounted component
} else {
setJourneys(something2) // trying to update unmounted component
}
} else {
setJourneys(something3); // trying to update unmounted component
}
};
// useEffects for continous loading of journeys.
useEffect(() => {
if (!hasLaterDepartures) setLoading(false);
}, [hasLaterDepartures]);
useEffect(() => {
if (hasLaterDepartures && journeys.length > 0) {
const latestStart = ... // just a date
if (latestStart.addMinutes(5).isSameDay(latestStart)) {
getJourneys(latestStart.addMinutes(5), journeys);
} else {
setLoading(false);
}
}
}, [journeys]);
I can't use a variable like isMounted = true in the useEffect beacuse it would reach inside the if statement and reach a "setState" by the time i'm on another page.
Moving the entire call into a useEffect doesn't seem to work either. I am at a loss.

Create a variable called mounted with useRef, initialised as true. Then add an effect to set mounted.current to false when the component unmounts.
You can use mounted.current anywhere inside the component to see if it's mounted, and check that before setting any state.
useRef gives you a variable you can mutate but which doesn't cause a rerender.

When you use useEffect hook with action which can be done after component change you should also take care about clean effect when needed. Maybe example help you, also check this page.
useEffect(() => {
let isClosed = false
const fetchData = async () => {
const data = await response.json()
if ( !isClosed ) {
setState( data )
}
};
fetchData()
return () => {
isClosed = true
};
}, []);

In your use case, you probably want to create a Store that doesn't reload everytime you change route (client side).
Example of a store using useContext();
const MyStoreContext = createContext()
export function useMyStore() {
const context = useContext(MyStoreContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useMyStore must be used within a MyStoreContext`)
}
return context
}
export function MyStoreProvider(props) {
const [ myState, setMyState ] = useState()
//....whatever codes u doing with ur hook.
const exampleCustomFunction = () => {
return myState
}
const getAllRoutes = async (mydestination) => {
return await getAllMyRoutesFromApi(mydestination)
}
// you return all your "getter" and "setter" in value props so you can use them outside the store.
return <MyStoreContext.Provider value={{ myState, setMyState, exampleCustomFunction, getAllRoutes }}>{props.children}</MyStoreContext.Provider>
}
You will wrap the store around your entire App, e.g.
<MyStoreProvider>
<App />
</MyStoreProvider>
In your page where you want to use your hook, you can do
const { myState, setMyState, exampleCustomFunction, getAllRoutes } = useMyStore()
const onClick = async () => getAllRouters(mydestination)
Considering if you have client side routing (not server side), this doesn't get reloaded every time you change your route.

Related

Why does useEffect trigger in first time render even though i gave it dependency arrays? [duplicate]

With React's new Effect Hooks, I can tell React to skip applying an effect if certain values haven't changed between re-renders - Example from React's docs:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
But the example above applies the effect upon initial render, and upon subsequent re-renders where count has changed. How can I tell React to skip the effect on the initial render?
As the guide states,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
In this example from the guide it's expected that count is 0 only on initial render:
const [count, setCount] = useState(0);
So it will work as componentDidUpdate with additional check:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
This is basically how custom hook that can be used instead of useEffect may work:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
return fn();
}
didMountRef.current = true;
}, inputs);
}
Credits go to #Tholle for suggesting useRef instead of setState.
Here's a custom hook that just provides a boolean flag to indicate whether the current render is the first render (when the component was mounted). It's about the same as some of the other answers but you can use the flag in a useEffect or the render function or anywhere else in the component you want. Maybe someone can propose a better name.
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
You can use it like:
import React, { useEffect } from 'react';
import { useIsMount } from './useIsMount';
const MyComponent = () => {
const isMount = useIsMount();
useEffect(() => {
if (isMount) {
console.log('First Render');
} else {
console.log('Subsequent Render');
}
});
return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};
And here's a test for it if you're interested:
import { renderHook } from '#testing-library/react-hooks';
import { useIsMount } from '../useIsMount';
describe('useIsMount', () => {
it('should be true on first render and false after', () => {
const { result, rerender } = renderHook(() => useIsMount());
expect(result.current).toEqual(true);
rerender();
expect(result.current).toEqual(false);
rerender();
expect(result.current).toEqual(false);
});
});
Our use case was to hide animated elements if the initial props indicate they should be hidden. On later renders if the props changed, we did want the elements to animate out.
I found a solution that is more simple and has no need to use another hook, but it has drawbacks.
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
This is just an example that there are others ways of doing it if your case is very simple.
The drawback of doing this is that you can't have a cleanup effect and will only execute when the dependency array changes the second time.
This isn't recommended to use and you should use what the other answers are saying, but I only added this here so people know that there is more than one way of doing this.
Edit:
Just to make it more clear, you shouldn't use this approach to solving the problem in the question (skipping the initial render), this is only for teaching purpose that shows you can do the same thing in different ways.
If you need to skip the initial render, please use the approach on other answers.
I use a regular state variable instead of a ref.
// Initializing didMount as false
const [didMount, setDidMount] = useState(false)
// Setting didMount to true upon mounting
useEffect(() => { setDidMount(true) }, [])
// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
We can refactor the didMount logic as a custom hook like this.
function useDidMount() {
const [didMount, setDidMount] = useState(false)
useEffect(() => { setDidMount(true) }, [])
return didMount
}
Finally, we can use it in our component like this.
const didMount = useDidMount()
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
UPDATE Using useRef hook to avoid the extra rerender (Thanks to #TomEsterez for the suggestion)
This time our custom hook returns a function returning our ref's current value. U can use the ref directly too, but I like this better.
function useDidMount() {
const mountRef = useRef(false);
useEffect(() => { mountRef.current = true }, []);
return () => mountRef.current;
}
Usage
const MyComponent = () => {
const didMount = useDidMount();
useEffect(() => {
if (didMount()) // do something
else // do something else
})
return (
<div>something</div>
);
}
On a side note, I've never had to use this hook and there are probably better ways to handle this which would be more aligned with the React programming model.
Let me introduce to you react-use.
npm install react-use
Wanna run:
only after first render? -------> useUpdateEffect
only once? -------> useEffectOnce
check is it first mount? -------> useFirstMountState
Want to run effect with deep compare, shallow compare or throttle? and much more here.
Don't want to install a library? Check the code & copy. (maybe a star for the good folks there too)
Best thing is one less thing for you to maintain.
A TypeScript and CRA friendly hook, replace it with useEffect, this hook works like useEffect but won't be triggered while the first render happens.
import * as React from 'react'
export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
const initializeRef = React.useRef<boolean>(false)
React.useEffect((...args) => {
if (initializeRef.current) {
cb(...args)
} else {
initializeRef.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dep)
}
Here is my implementation based on Estus Flask's answer written in Typescript. It also supports cleanup callback.
import { DependencyList, EffectCallback, useEffect, useRef } from 'react';
export function useDidUpdateEffect(
effect: EffectCallback,
deps?: DependencyList
) {
// a flag to check if the component did mount (first render's passed)
// it's unrelated to the rendering process so we don't useState here
const didMountRef = useRef(false);
// effect callback runs when the dependency array changes, it also runs
// after the component mounted for the first time.
useEffect(() => {
// if so, mark the component as mounted and skip the first effect call
if (!didMountRef.current) {
didMountRef.current = true;
} else {
// subsequent useEffect callback invocations will execute the effect as normal
return effect();
}
}, deps);
}
Live Demo
The live demo below demonstrates the different between useEffect and useDidUpdateEffect hooks
I was going to comment on the currently accepted answer, but ran out of space!
Firstly, it's important to move away from thinking in terms of lifecycle events when using functional components. Think in terms of prop/state changes. I had a similar situation where I only wanted a particular useEffect function to fire when a particular prop (parentValue in my case) changes from its initial state. So, I created a ref that was based on its initial value:
const parentValueRef = useRef(parentValue);
and then included the following at the start of the useEffect fn:
if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;
(Basically, don't run the effect if parentValue hasn't changed. Update the ref if it has changed, ready for the next check, and continue to run the effect)
So, although other solutions suggested will solve the particular use-case you've provided, it will help in the long run to change how you think in relation to functional components.
Think of them as primarily rendering a component based on some props.
If you genuinely need some local state, then useState will provide that, but don't assume your problem will be solved by storing local state.
If you have some code that will alter your props during a render, this 'side-effect' needs to be wrapped in a useEffect, but the purpose of this is to have a clean render that isn't affected by something changing as it's rendering. The useEffect hook will be run after the render has completed and, as you've pointed out, it's run with every render - unless the second parameter is used to supply a list of props/states to identify what changed items will cause it to be run subsequent times.
Good luck on your journey to Functional Components / Hooks! Sometimes it's necessary to unlearn something to get to grips with a new way of doing things :)
This is an excellent primer: https://overreacted.io/a-complete-guide-to-useeffect/
Below solution is similar to above, just a little cleaner way i prefer.
const [isMount, setIsMount] = useState(true);
useEffect(()=>{
if(isMount){
setIsMount(false);
return;
}
//Do anything here for 2nd render onwards
}, [args])
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Example:
useEffectAfterMount(() => {
document.title = `You clicked ${count} times`;
}, [count])

Can you use an async function to set initial state with useState

My component relies on local state (useState), but the initial value should come from an http response.
Can I pass an async function to set the initial state? How can I set the initial state from the response?
This is my code
const fcads = () => {
let good;
Axios.get(`/admin/getallads`).then((res) => {
good = res.data.map((item) => item._id);
});
return good;
};
const [allads, setAllads] = useState(() => fcads());
But when I try console.log(allads) I got result undefined.
If you use a function as an argument for useState it has to be synchronous.
The code your example shows is asynchronous - it uses a promise that sets the value only after the request is completed
You are trying to load data when a component is rendered for the first time - this is a very common use case and there are many libraries that handle it, like these popular choices: https://www.npmjs.com/package/react-async-hook and https://www.npmjs.com/package/#react-hook/async. They would not only set the data to display, but provide you a flag to use and show a loader or display an error if such has happened
This is basically how you would set initial state when you have to set it asynchronously
const [allads, setAllads] = useState([]);
const [loading, setLoading] = useState(false);
React.useEffect(() => {
// Show a loading animation/message while loading
setLoading(true);
// Invoke async request
Axios.get(`/admin/getallads`).then((res) => {
const ads = res.data.map((item) => item._id);
// Set some items after a successful response
setAllAds(ads):
})
.catch(e => alert(`Getting data failed: ${e.message}`))
.finally(() => setLoading(false))
// No variable dependencies means this would run only once after the first render
}, []);
Think of the initial value of useState as something raw that you can set immediately. You know you would be display handling a list (array) of items, then the initial value should be an empty array. useState only accept a function to cover a bit more expensive cases that would otherwise get evaluated on each render pass. Like reading from local/session storage
const [allads, setAllads] = useState(() => {
const asText = localStorage.getItem('myStoredList');
const ads = asText ? JSON.parse(asText) : [];
return ads;
});
You can use the custom hook to include a callback function for useState with use-state-with-callback npm package.
npm install use-state-with-callback
For your case:
import React from "react";
import Axios from "axios";
import useStateWithCallback from "use-state-with-callback";
export default function App() {
const [allads, setAllads] = useStateWithCallback([], (allads) => {
let good;
Axios.get("https://fakestoreapi.com/products").then((res) => {
good = res.data.map((item) => item.id);
console.log(good);
setAllads(good);
});
});
return (
<div className="App">
<h1> {allads} </h1>
</div>
);
}
Demo & Code: https://codesandbox.io/s/distracted-torvalds-s5c8c?file=/src/App.js

In React, failing to stop Axios request when component unmounts

With data fetching in React, the following is a common warning:
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 ParentComponent
I've read multiple posts and suggestions on how to handle this, and none are working currently.
For this, we have the function useAxiosApi which fetches data asynchronously, and ParentComponent which is the component that uses the useAxiosApi() and needs the data. ParentComponent is the component being unmounted / being referenced in the warnings.
Parent Component
import useAxiosApi...
function ParentComponent({ info }) {
const dataConfig = { season: info.season, scope: info.scope };
const [data, isLoading1, isError1] = useAxiosApi('this-endpoint', [], dataConfig);
return (
{isLoading && <p>We are loading...</p>}
{!isLoading &&
... use the data to render something...
}
)
}
useAxiosApi
import axios from 'axios';
import { useState } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';
const resources = {};
const useAxiosApi = (endpoint, initialValue, config) => {
// Set Data-Fetching State
const [data, setData] = useState(initialValue);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// Use in lieu of useEffect
useDeepCompareEffect(() => {
// Token/Source should be created before "fetchData"
let source = axios.CancelToken.source();
let isMounted = true;
// Create Function that makes Axios requests
const fetchData = async () => {
// For Live Search on keystroke, Save Fetches and Skip Fetch if Already Made
if (endpoint === 'liveSearch' && resources[config.searchText]) {
return [resources[config.searchText], false, false];
}
// Otherwise, Continue Forward
setIsError(false);
setIsLoading(true);
try {
const url = createUrl(endpoint, config);
const result = await axios.get(url, { cancelToken: source.token });
console.log('isMounted: ', isMounted);
if (isMounted) {
setData(result.data);
}
// If LiveSearch, store the response to "resources"
if (endpoint === 'liveSearch') {
resources[config.searchText] = result.data;
}
} catch (error) {
setIsError(true);
} finally {
setIsLoading(false);
}
};
// Call Function
fetchData();
// Cancel Request if needed in cleanup function
return () => {
console.log('Unmount or New Search? About to call source.cancel()');
isMounted = false; // is this doing its job?
source.cancel();
};
}, [endpoint, config]);
// Return as length-3 array
return [data, isLoading, isError];
};
export default useAxiosApi;
createUrl is simply a function that takes the endpoint and dataConfig and creates the url that axios will fetch from. Note that our cancelTokens seem to be working in conjunction with the Live search, as new searches are cancelling the old search queries, and the saving of data results into resources for the one specific endpoint liveSearch works as well.
However, our problem is that when ParentComponent is unmounted quickly, before the data fetch is complete, we still receive the Cant perform a React state update warning. I've checked the console.logs(), and console.log('isMounted: ', isMounted) is always returning true, even if we unmount the component quickly after it is mounted / before data fetching is complete.
We're at a loss on this, as using the isMounted variable is the way that I've seen this problem handled before. Perhaps there's a problem with the useDeepCompareEffect hook? Or maybe we're missing something else.
Edit: Weve also tried to create the isMounted variable from inside of ParentComponent, and pass that as a parameter into the useAxiosApi function, however this did not work for us either... In general, it would be much better if we can handle this warning via an update to our useAxiosApi function, as opposed to in the ParentComponent.
Edit2: It seems like the cancelToken only works when a duplicate API call is fired off to the same endpoint. This is good for our liveSearch, however it means that all of the other fetches are not cancelled.

React customHook returns correct data after second call

I have a problem because whenever I try to return data from my custom useValidation hook it returns it correctly after second call.
For example here is my component that uses custom hook.
const { runValidation, errors } = useValidation();
return (<button onClick={runValidation}>Test</button>) // Returns {} on first click and finally {...data} on second
Then inside my useValidation hook I have state for errors:
const [errors, setErrors] = useState({});
const validate = () => {
setErrors(data);
}
const runValidation = () => {
validate();
};
return { runValidation, errors };
How can I fix this issue?
This is the expected behaviour.
Your problem is that you access the data directly after setting the state, that is before the component is updated. Therefore you'll only see the previous data.
Try to display the state in your component rather than logging it.
You'll see the state correctly updated.
const { runValidation, errors } = useValidation();
// will display the correct state after update
return (<button onClick={runValidation}>{errors.key}</button>);
const useValidation = () => {
const [errors, setErrors] = useState({});
const validate = () => {
setErrors({ key: 123 });
};
const runValidation = () => {
validate();
console.log(errors); // will log {} immediately after
};
return { runValidation, errors };
};
When you update the sate, react doesn't immediately update it. The value is passed into a queue and then updated on the next render. You are actually always printing the previous value, which you can't notice in your example, because the value is always the same.
When you call setState what react does is, it throws away your whole component and renders it again with the new value. You can't access the new value in the same step. Since you do the console.log in the same function/step with setErrors, it will show the previous value. This is because the component hasn't been updated with new values yet.

Is there a way to check if the react component is unmounted?

I have a usecase where i need to unmount my react component. But in some cases, the particular react component is unmounted by a different function.
Hence, I need to check if the component is mounted before unmounting it.
Since isMounted() is being officially deprecated, you can do this in your component:
componentDidMount() {
this._ismounted = true;
}
componentWillUnmount() {
this._ismounted = false;
}
This pattern of maintaining your own state variable is detailed in the ReactJS documentation: isMounted is an Antipattern.
I'll be recommended you to use the useRef hook for keeping track of component is mounted or not because whenever you update the state then react will re-render the whole component and also it will trigger the execution of useEffect or other hooks.
function MyComponent(props: Props) {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
return () => { isMounted.current = false }
}, []);
return (...);
}
export default MyComponent;
and you check if the component is mounted with if (isMounted.current) ...
I think that Shubham answer is a workaround suggested by react for people that need to transition their code to stop using the isMounted anti-pattern.
This is not necessarily bad, but It's worth listing the real solutions to this problem.
The article linked by Shubham offers 2 suggestions to avoid this anti pattern. The one you need depends on why you are calling setState when the component is unmounted.
if you are using a Flux store in your component, you must unsubscribe in componentWillUnmount
class MyComponent extends React.Component {
componentDidMount() {
mydatastore.subscribe(this);
}
render() {
...
}
componentWillUnmount() {
mydatastore.unsubscribe(this);
}
}
If you use ES6 promises, you may need to wrap your promise in order to make it cancelable.
const cancelablePromise = makeCancelable(
new Promise(r => component.setState({...}}))
);
cancelablePromise
.promise
.then(() => console.log('resolved'))
.catch((reason) => console.log('isCanceled', reason.isCanceled));
cancelablePromise.cancel(); // Cancel the promise
Read more about makeCancelable in the linked article.
In conclusion, do not try to patch this issue by setting variables and checking if the component is mounted, go to the root of the problem. Please comment with other common cases if you can come up with any.
Another solution would be using Refs . If you are using React 16.3+, make a ref to your top level item in the render function.
Then simply check if ref.current is null or not.
Example:
class MyClass extends React.Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
}
checkIfMounted() {
return this.elementRef.current != null;
}
render() {
return (
<div ref={this.elementRef} />
);
}
}
Using #DerekSoike answer, however in my case using useState to control the mounted state didn't work since the state resurrected when it didn't have to
What worked for me was using a single variable
myFunct was called in a setTimeout, and my guess is that when the same component initialized the hook in another page it resurrected the state causing the memory leak to appear again
So this didn't work for me
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
return () => setIsMounted(false)
}, [])
const myFunct = () => {
console.log(isMounted) // not always false
if (!isMounted) return
// change a state
}
And this did work for me
let stillMounted = { value: false }
useEffect(() => {
stillMounted.value = true
return () => (stillMounted.value = false)
}, [])
const myFunct = () => {
if (!stillMounted.value) return
// change a state
}
I got here because I was looking for a way to stop polling the API.
The react docs does cover the websocket case, but not the polling one.
The way I worked around it
// React component
React.createClass({
poll () {
if (this.unmounted) {
return
}
// otherwise, call the api
}
componentWillUnmount () {
this.unmounted = true
}
})
it works. Hope it helps
Please, let me know if you guys know any failing test case for this =]
If you're using hooks:
function MyComponent(props: Props) {
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
return () => {
setIsMounted(false);
}
}, []);
return (...);
}
export default MyComponent;
The same idea but enother implementation
/**
* component with async action within
*
* #public
*/
class MyComponent extends Component {
constructor ( ...args ) {
// do not forget about super =)
super(...args);
// NOTE correct store "setState"
let originSetState = this.setState.bind(this);
// NOTE override
this.setState = ( ...args ) => !this.isUnmounted&&originSetState(...args);
}
/**
* no necessary setup flag on component mount
* #public
*/
componentWillUnmount() {
// NOTE setup flag
this.isUnmounted = true;
}
/**
*
* #public
*/
myCustomAsyncAction () {
// ... code
this.setState({any: 'data'}); // do not care about component status
// ... code
}
render () { /* ... */ }
}
I have solve with hot reload and react to different it events ✅
const {checkIsMounted} = useIsMounted(); //hook from above
useEffect(() => {
//here run code
return () => {
//hot reload fix
setTimeout(() => {
if (!checkIsMounted()) {
//here we do unmount action
}
}, 100);
};
}, []);
Pproblem
There is a problem when using the useState() hook. If you are also trying to do something else in a useEffect function (like fetching some data when the component is mounted) at the same time with setting the new value for the hook,
const [isMounted, setIsMounted] = useState(false)
useEffect(() =>
{
setIsMounted(true) //should be true
const value = await fetch(...)
if (isMounted) //false still
{
setValue(value)
}
return () =>
{
setIsMounted(false)
}
}, [])
the value of the hook will remain the same as the initial value (false), even if you have changed it in the beggining. It will remain unchanged for that first render, a new re-render being required for the new value to be applied.
For some reason #GWorking solution did not work too. The gap appears to happen while fetching, so when data arrives the component is already unmounted.
Solution
You can just combine both and and check if the component is unmounted during any re-render and just use a separate variable that will keep track to see if the component is still mounted during that render time period
const [isMounted, setIsMounted] = useState(false)
let stillMounted = { value: false }
useEffect(() =>
{
setIsMounted(true)
stillMounted.value = true
const value = await fetch(...)
if (isMounted || stillMounted.value) //isMounted or stillMounted
{
setValue(value)
}
return () =>
{
(stillMounted.value = false)
setIsMounted(false)
}
}, [isMounted]) //you need to also include Mounted values
Hope that helps someone!
There's a simple way to avoid 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.
You can redefine setState method inside your class component using this pattern:
componentWillUnmount() {
this._unmounted = true;
}
setState(params, callback) {
this._unmounted || super.setState(params, callback);
}
i found that the component will be unmounted, generate fill this var
if(!this._calledComponentWillUnmount)this.setState({vars});
You can use:
myComponent.updater.isMounted(myComponent)
"myComponent" is instance of your react component.
this will return 'true' if component is mounted and 'false' if its not..
This is not supported way to do it. you better unsubscribe any async/events
on componentWillUnmount.

Categories

Resources