How is this simple useEffect() causing a memory leak? - javascript

I only use useEffect() in one place in my app as follows:
useEffect(() => {
document.body.addEventListener('click', bodyClicked);
return () => {
document.body.removeEventListener('click', bodyClicked);
};
}, [bodyClicked]);
function bodyClicked() {
dispatch({type: 'toggleMenuPageOff'});
}
I am getting this warning:
react_devtools_backend.js:4061 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.
at WEBPACK_DEFAULT_EXPORT (http://localhost:3000/bundle.js:4146:70)
at div
Why is this a memory leak and how can I write it correctly to remove the warning?

You're creating an infinite loop. the function you're if inside the components code is being recreated on each rerender which means it will trigger useEffect and therefore be called again and again...
https://reactjs.org/docs/hooks-reference.html#usecallback

useEffect is regsitered with DOM and when the component is done running, then it is done running, your component is no longer in dom but browser is still listening to click event or in other words you are trying to perform click action after the component is unmounted this leads to memory leaks. Anything inside is useEffect is sideeffect and you gotta clean up the code to remove potential consequences
just inside of useeffect() add clean up code which is again a function that has return response of your useEffect() that actually clean up your code sideeffects
in your code you are using body clciked in your dependancies array keep that to [] empty
useEffect(()=> {
function bodyClicked(){
window.bodyclicked(.....)}
return function(){
window.removeeventlistener("click",bodyClicked)}
}},[])

Related

React hooks: Why do several useState setters in an async function cause several rerenders?

This following onClick callback function will cause 1 re-render:
const handleClickSync = () => {
// Order of setters doesn't matter - React lumps all state changes together
// The result is one single re-rendering
setValue("two");
setIsCondition(true);
setNumber(2);
};
React lumps all three state changes together and causes 1 rerender.
The following onClick callback function, however, will cause 3 re-renderings:
const handleClickAsync = () => {
setTimeout(() => {
// Inside of an async function (here: setTimeout) the order of setter functions matters.
setValue("two");
setIsCondition(true);
setNumber(2);
});
};
It's one re-render for every useState setter. Furthermore the order of the setters influences the values in each of these renderings.
Question: Why does the fact that I make the function async (here via setTimeout) cause the state changes to happen one after the other and thereby causing 3 re-renders. Why does React lump these state changes together if the function is synchronous to only cause one rerender?
You can play around with this CodeSandBox to experience the behavior.
In react 17, if code execution starts inside of react (eg, an onClick listener or a useEffect), then react can be sure that after you've done all your state-setting, execution will return to react and it can continue from there. So for these cases, it can let code execution continue, wait for the return, and then synchronously do a single render.
But if code execution starts randomly (eg, in a setTimeout, or by resolving a promise), then code isn't going to return to react when you're done. So from react's perspective, it was quietly sleeping and then you call setState, forcing react to be like "ahhh! they're setting state! I'd better render". There are async ways that react could wait to see if you're doing anything more (eg, a timeout 0 or a microtask), but there isn't a synchronous way for react to know when you're done.
You can tell react to batch multiple changes by using unstable_batchedUpdates:
import { unstable_batchedUpdates } from "react-dom";
const handleClickAsync = () => {
setTimeout(() => {
unstable_batchedUpdates(() => {
setValue("two");
setIsCondition(true);
setNumber(2);
});
});
};
In version 18 this isn't necessary, since the changes they've made to rendering for concurrent rendering make batching work for all cases.
Right now react only batches sync setStates inside event handlers.
But in react 18 it will be available in setTimeout, useEffects etc
Here is excellent explanation from Dan https://github.com/reactwg/react-18/discussions/21
UPDATE: REACT 18 FEATURES AUTOMATIC BATCHING
In React, Batching helps to reduce the number of re-renders that happen when state changes, when you call setState(). Previously, React batched state updates in event handlers, for example :
const handleClick = () => {
setCounter();
setActive();
setValue();
}
//re-rendered once at the end.
However, state updates that happened outside of event handlers were not batched. For example, if you had a promise or were making a network call, the state updates would not be batched. Like this:
fetch('/network').then( () => {
setCounter(); //re-rendered 1 times
setActive(); //re-rendered 2 times
setValue(); //re-rendered 3 times
});
//Total 3 re-renders
As you can tell, this is not performant. React 18 introduces automatic batching which allows all state updates – even within promises, setTimeouts, and event callbacks – to be batched. This significantly reduces the work that React has to do in the background. React will wait for a micro-task to finish before re-rendering.
Automatic batching is available out of the box in React, but if you want to opt-out you can use flushSync.
More Reading from FREECODECAMP RESOURCES: https://www.freecodecamp.org/news/react-18-new-features/

React hook - useEffect missing dependencies warning

I am not sure if this is a valid warning by using useEffect around the dependency array, it seems like whenever variable, method or dispatch inside the useEffect are giving warning that
React Hook useEffect has missing dependencies: 'active', 'retrieveUser', and 'dispatch'. Either include them or remove the dependency array of following example, if I just leave it as blank array to perform the componentDidMount functionality
useEffect(() => {
setActive(active);
await retrieveUser(param1, param2);
dispatch(someAction);
}, []). // warning: React Hook useEffect has missing dependencies: 'active', 'retrieveUser', 'param1', 'param2', and 'dispatch'. Either include them or remove the dependency array, but here I just want to perform componentDidMount concept so my dependency list has to be empty
or
useEffect(() => {
await retrieveUser(param1, param2);
dispatch(someAction);
}, [userId]). // warning: React Hook useEffect has missing dependencies: 'retrieveUser', 'param1', 'param2', and 'dispatch'. Either include them or remove the dependency array
Are those valid warning? Especially that I just want to monitor on specific data field, what's the point of adding all inside dispatch or method into the dependency array if I can't add anything into the dependency list(for componentDidMOunt) or I just want to monitor userId, not param1, param2, etc
React is giving this warning because you are using part of your component's state or props in useEffect but have told it not to run the useEffect callback when that state/props changes. React assumes you will want to run that useEffect() whenever the referenced states change so it is giving you a warning. If you were only referencing variables outside of state or props, you would not get this warning. See below for info about conditionally running useEffect.
Since your goal is to only run useEffect only sometimes, you're likely going to need to restructure your component so that different data is in or out of state. It's difficult to recommend more specific solutions without knowing more about the component; but, a good goal is to have the state you utilize in useEffect match up with the changes that you want to trigger a rerender.
There might be other issues with state in your component. For example, I notice that you call setActive(active);. If active is part of the component's state, that means you are setting active to itself. Which isn't necessary. (this is assuming you are following typical naming patterns and likely have a line at the top of your component like:
const [active, setActive] = useState(initialValue);
Conditionally running useEffect
When you provide a second argument to useEffect(), it limits the situations when useEffect runs the callback function on rerenders. This is for performance reasons as explained in the React docs.
In the first useEffect where you provide an empty array, you are telling React to only run those actions (setActive(active)...etc.) when the component mounts and unmounts.
In the second useEffect where you provide an array with userId, you are telling React to only run the callback on mount, unmount, and whenever userId changes.
React is giving you that warning because it knows it won't run useEffect if active...etc or other values change. This can cause different parts of your component to be referencing different states. For example, one part of your component might be using userId = 1 and another part is using userId = 2. This is not how React is designed to work.
Create a hook that executes only once:
const useEffectOnce = effect => {
useEffect(effect, []);
};
use this hook inside your component
useEffectOnce(() => {
const request = async () => {
const result = await retrieveSum(param1, param2);
setResult(result);
};
request();
});
Now, even if your props (param1, param2) change, the effect is called only once. Be very careful when doing this, the component is probably buggy unless your initial props are truly special. I don't see how this is different from disabling the rule with
// eslint-disable-next-line react-hooks/exhaustive-deps
Demo: https://codesandbox.io/s/trusting-satoshi-fngrw?file=/src/App.js

How can we implement componentWillUnmount using react hooks?

The method componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. If we use useEffect with an empty array ([]) as the second argument and put our function in return statement it will be executed after the component is unmounted and even after another component will be mounted. This is done for performance reasons as far as I understand. In order not to delay rendering.
So the question is - how can we call some function using hooks before a component gets unmounted?
What I am trying to do is an application which saves user's input as he types (without submitting form). I use setInterval to save updated text every N seconds. And I need to force save updates before the component will unmount. I don't want to use prompt by react router before navigating. This is an electron application. I appreciate any thoughts or advice on how to implement such functionality.
Update
Unfortunately, Effects with Cleanup run after letting the browser paint. More details can be found here: So What About Cleanup?. It basically means that cleanup is run after a component is unmounted and it is not the same as executing code in componentWillUnmount(). I can clearly see the sequence of calls if I put console.log statements in the cleanup code and in another component. The question is whether we can execute some code before a component is unmounted using hooks.
Update2
As I can see I should better describe my use case. Let's imagine a theoretical app which holds its data in a Redux store. And we have two components with some forms. For simplicity, we don't have any backend or any async logic. We use only Redux store as data storage.
We don't want to update Redux store on every keystroke. So we keep actual values in the local component's state which we initialize with values from the store when a component mounts. We also create an effect which sets up a setInterval for 1s.
We have the following process. A User types something. Updates are stored in the local component state until our setInterval callback is called. The callback just puts data in the store (dispatches action). We put our callback in the useEffect return statement to force save to store when the component gets unmounted because we want to save data to store in this case as soon as possible.
The problem comes when a user types something in the first component and immediately goes to the second component (faster than 1s). Since the cleanup in our first component will be called after re-rendering, our store won't be updated before the second component gets mounted. And because of that, the second component will get outdated values to its local state.
If we put our callback in componentWillUnmount() it will be called before unmounting and the store will be updated before the next component mounts. So can we implement this using hooks?
componentWillUnmount can be simulated by returning a function inside the useEffect hook. The returned function will be called just before every rerendering of the component. Strictly speaking, this is the same thing but you should be able to simulate any behaviour you want using this.
useEffect(() => {
const unsubscribe = api.createSubscription()
return () => unsubscribe()
})
Update
The above will run every time there is a rerender. However, to simulate the behaviour only on mounting and unmounting (i.e. componentDidMount and componentWillUnmount). useEffect takes a second argument which needs to be an empty array.
useEffect(() => {
const unsubscribe = api.createSubscription()
return () => unsubscribe()
}, [])
See a more detailed explanation of the same question here.
Since the introduction of the useLayoutEffect hook, you can now do
useLayoutEffect(() => () => {
// Your code here.
}, [])
to simulate componentWillUnmount. This runs during unmount, but before the element has actually left the page.
The question here is how do you run code with hooks BEFORE unmount? The return function with hooks runs AFTER unmount and whilst that doesn’t make a difference for most use cases, their are some where it is a critical difference.
Having done a bit of investigation on this, I have come to the conclusion that currently hooks simply does not provide a direct alternative to componentWillUnmount. So if you have a use case that needs it, which is mainly for me at least, the integration of non-React libs, you just have to do it the old way and use a component.
Update: see the answer below about UseLayoutEffect() which looks like it may solve this issue.
I agree with Frank, but the code needs to look like this otherwise it will run only on the first render:
useLayoutEffect(() => {
return () => {
// Your code here.
}
}, [])
This is equivalent to ComponentWillUnmount
Similar to #pritam's answer, but with an abstracted code example. The whole idea of useRef is to allow you to keep track of the changes to the callback and not have a stale closure at the time of execution. Hence, the useEffect at the bottom can have an empty dependency array to ensure it only runs when the component unmounts. See the code demo.
Reusable hook:
type Noop = () => void;
const useComponentWillUnmount = (callback: Noop) => {
const mem = useRef<Noop>();
useEffect(() => {
mem.current = callback;
}, [callback]);
useEffect(() => {
return () => {
const func = mem.current as Noop;
func();
};
}, []);
};
After a bit of research, found that - you could still accomplish this. Bit tricky but should work.
You can make use of useRef and store the props to be used within a closure such as render useEffect return callback method
function Home(props) {
const val = React.useRef();
React.useEffect(
() => {
val.current = props;
},
[props]
);
React.useEffect(() => {
return () => {
console.log(props, val.current);
};
}, []);
return <div>Home</div>;
}
DEMO
However a better way is to pass on the second argument to useEffect so that the cleanup and initialisation happens on any change of desired props
React.useEffect(() => {
return () => {
console.log(props.current);
};
}, [props.current]);
I got in a unique situation where the useEffect(() => () => { ... }, []); answers did not work for me. This is because my component never got rendered — I was throwing an exception before I could register the useEffect hook.
function Component() {
useEffect(() => () => { console.log("Cleanup!"); }, []);
if (promise) throw promise;
if (error) throw error;
return <h1>Got value: {value}</h1>;
}
In the above example, by throwing a Promise<T> that tells react to suspend until the promise is resolved. However, once the promise is resolved, an error is thrown. Since the component never gets rendered and goes straight to an ErrorBoundary, the useEffect() hook is never registered!
If you're in a similar situation as myself, this little code may help:
To solve this, I modified my ErrorBoundary code to run a list of teardowns once it was recovered
export default class ErrorBoundary extends Component {
// ...
recover() {
runTeardowns();
// ...
}
// ...
}
Then, I created a useTeardown hook which would add teardowns that needed to be ran, or make use of useEffect if possible. You'll most likely need to modify it if you have nesting of error boundaries, but for my simple usecase, it worked wonderfully.
import React, { useEffect, useMemo } from "react";
const isDebugMode = import.meta.env.NODE_ENV === "development";
const teardowns: (() => void)[] = [];
export function runTeardowns() {
const wiped = teardowns.splice(0, teardowns.length);
for (const teardown of wiped) {
teardown();
}
}
type Teardown = { registered?: boolean; called?: boolean; pushed?: boolean } & (() => unknown);
/**
* Guarantees a function to run on teardown, even when errors occur.
*
* This is necessary because `useEffect` only runs when the component doesn't throw an error.
* If the component throws an error before anything renders, then `useEffect` won't register a
* cleanup handler to run. This hook **guarantees** that a function is called when the component ends.
*
* This works by telling `ErrorBoundary` that we have a function we would like to call on teardown.
* However, if we register a `useEffect` hook, then we don't tell `ErrorBoundary` that.
*/
export default function useTeardown(onTeardown: () => Teardown, deps: React.DependencyList) {
// We have state we need to maintain about our teardown that we need to persist
// to other layers of the application. To do that, we store state on the callback
// itself - but to do that, we need to guarantee that the callback is stable. We
// achieve this by memoizing the teardown function.
const teardown = useMemo(onTeardown, deps);
// Here, we register a `useEffect` hook to run. This will be the "happy path" for
// our teardown function, as if the component renders, we can let React guarantee
// us for the cleanup function to be ran.
useEffect(() => {
// If the effect gets called, that means we can rely on React to run our cleanup
// handler.
teardown.registered = true;
return () => {
if (isDebugMode) {
// We want to ensure that this impossible state is never reached. When the
// `runTeardowns` function is called, it should only be ran for teardowns
// that have not been able to be hook into `useEffect`.
if (teardown.called) throw new Error("teardown already called, but unregistering in useEffect");
}
teardown();
if (isDebugMode) {
// Because `teardown.registered` will already cover the case where the effect
// handler is in charge of running the teardown, this isn't necessary. However,
// this helps us prevent impossible states.
teardown.called = true;
}
};
}, deps);
// Here, we register the "sad path". If there is an exception immediately thrown,
// then the `useEffect` cleanup handler will never be ran.
//
// We rely on the behavior that our custom `ErrorBoundary` component will always
// be rendered in the event of errors. Thus, we expect that component to call
// `runTeardowns` whenever it deems it appropriate to run our teardowns.
// Because `useTeardown` will get called multiple times, we want to ensure we only
// register the teardown once.
if (!teardown.pushed) {
teardown.pushed = true;
teardowns.push(() => {
const useEffectWillCleanUpTeardown = teardown.registered;
if (!useEffectWillCleanUpTeardown) {
if (isDebugMode) {
// If the useEffect handler was already called, there should be no way to
// re-run this teardown. The only way this impossible state can be reached
// is if a teardown is called multiple times, which should not happen during
// normal execution.
const teardownAlreadyCalled = teardown.called;
if (teardownAlreadyCalled) throw new Error("teardown already called yet running it in runTeardowns");
}
teardown();
if (isDebugMode) {
// Notify that this teardown has been called - useful for ensuring that we
// cannot reach any impossible states.
teardown.called = true;
}
}
});
}
}
It does not matter wether the returned function from useEffect gets called before or after the component unmounted: You still have access to the states valuey through the closure:
const [input, setInput] = useState(() => Store.retrieveInput());
useEffect(() => {
return () => Store.storeInput(input); // < you can access "input" here, even if the component unmounted already
}, []);
If you don't manage the input in the components state, your whole structure is broken and should be changed to manage state at the right place. In your case, you should lift the shared input state of the components to the parent.
ReactJS docs on hooks specify this:
Effects may also optionally specify how to “clean up” after them by
returning a function.
So any function you return in your useEffect hook, will be executed when the component unmounts, as well as before re-running the effect due to a subsequent render.

Is it necessary to unmount state within ComponentWillUnmount?

I'm performing server requests in my app within componentDidMount.So I'm calling setState within componentDidMount.Do I need to unmount this state within componentWillUnmount ?Is this is a solution for avoiding memory leaks in my app ?Please help me to find a solution for this.Thank you!
sample code
componentDidMount(){
fetch({ /* ... */ })
.then(res => res.json())
.then((responseData) => {
this.setState({
result: responseData.meta.data
})
})
}
componentWillUnmount(){
this.setState({
result:''
})
}
It's not needed to unmount the state. Setting result to empty string isn't any better than setting it to any other value.
The cause of memory leaks is that a reference to an object (component instance) is used somewhere, this prevents it from being garbage-collected as unused.
In this piece of code setState can be called after the component is unmounted because the request isn't cancelled. This will cause a 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.
In case a request is long enough, this will cause a memory leak. In order to avoid it, a request or a promise that causes setState call needs to be cancelled. As for Fetch API request, can be done with AbortController.
React cleans the state of the component upon unmounting, so you don't have to reset the state on componentWillUnmount.
Something that can happen with performing request in components, is that your request can be completed after your component is unmounted again. At that point you're trying to perform setState on a component that isn't mounted. You'll receive an error in your console at that point.
Most HTTP libraries offer functionalities to cancel requests, this can be used to prevent that from happening. An example from the axios library here...
You wouldnt have to worry about cleaning up state while unmounting, as the reasons are already being highlighted by the people above.
But if you have left the listeners attached to your component "unremoved", then that might be a possible candidate for memory leak.
componentDidMount() can be used as a place to attach listeners and componentWillUnmount to remove those attached listeners.'
In Flux architecture, we use them to attach and remove listeners for events emitted from stores.
public componentDidMount() {
sampleStore.addListener('sampleEventFired', this.oncatch);
}
public componentWillUnmount() {
sampleStore.removeListener('sampleEventFired', this.oncatch);
}

react-redux store not updating within onClick function

I'm experiencing this weird issue where my react-redux store is updating, but is not updating within the function that calls the actions.
this.props.active is undefined, then I set it to an integer with this.props.actions.activeSet(activeProc), but it remains undefined and enters the next if condition.
I know my app is working because everything else works with this.props.active having the correct value.
Is this supposed to happen?
edit:
After doing some testing, it appears that the state remains the same inside the onClick function.
All calls to console.log(this.props) made within the onClick function show no change to the state, but adding setTimeout(() => {console.log(this.props)}, 1) at the end to test shows that the state is being updated.
Other parts of the app are working as intended, with state changes applied immediately.
But I still don't understand what is going on.
Component function code
() => {
console.log(this.props.active); // undefined
if (this.props.active === undefined && this.props.readyQueue.length > 0) {
let activeProc = this.props.readyQueue[0];
this.props.actions.readyPop();
this.props.actions.activeSet(activeProc); // set to an integer
this.props.actions.execStateSet("Running");
}
console.log(this.props.active); // still remains undefined
if (this.props.active === undefined) {
this.props.actions.execStateSet("Idle");
}
}
function mapStateToProps(state, props) {
return {
active: state.ProcessReducer.active,
};
}
Action code
export const activeSet = (procId) => {
return {
type: 'ACTIVE_SET',
procId
}
}
Reducer code
case 'ACTIVE_SET':
return Object.assign({}, state, {
active: action.procId
});
Your Redux state updates synchronously with the dispatch of your action. Your reducer has executed by the time the dispatch call returns.
However, React isn't Redux. Redux tells React-Redux's wrapper component that the state has changed. This also happens before dispatch returns.
React-Redux then tells React that the component needs to be rerendered by calling forceUpdate. React then waits until it feels it's a good time to take care of that. I haven't looked, but it probably uses setImmediate or equivalent but it's async. This allows React to batch updates and maybe there are other reasons.
In any case, the React-Redux wrapper component will get rendered by React when the time comes and it'll use your mapStateToProps to distill theprops out of the state and then passes them to React as props for your actual component. Then, when React feels it's an okay time, it calls your render method or function. It may do all kinds of things in before that, such as calling componentWillReceiveProps or rendering some other component that also needs rendering. In any case it's none of our business. React does its thing. But when your Render function is called, your props will now reflect the new state.
You shouldn't rely on new state in an onClick handler. The onClick should only call the bound action creator, which I guess is now more aptly called an action dispatcher. If something needs to be done with the new state, you should use Redux-Thunk middleware and create a thunked action creator. These have access to getState and if they don't perform any internal async stuff, then the entire action can actually be just as synchronous as a simple dispatch (not that you'd need that in a simple onClick handler).
Finally, React is very asynchronous in nature. Think of it as telling React what you want (component + props) and letting React take it from there. If React needs to know how to turn a component into DOM elements, it'll call your component's render function. How or when React does is thing is an implementation detail that doesn't concern us.

Categories

Resources