When use ComponentDidMount() I found this error : Can't call setState - javascript

I found this error :
Can't call setState (or forceUpdate) 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.
Context : When I'm connected, I'm on the homepage, this page not contain the breadCrumb, but If I go on CampaignPage (also the name of the component), I have the BreadCrumb (Component name) I found this error.
On other post what I could see, they said probably problem on asynchronously on ComponentWillMount but I think my problem is different and I can't find a solution.
My code look like that :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import objectAssign from 'object-assign';
import { withRouter } from 'react-router';
import {
BREADCRUMBS_ROUTES,
BREADCRUMBS_ROUTES_FOR_ID,
BREADCRUMBS_ENDPOINT
} from 'constants';
import { getEntityById, setUpdatedBreadcrumbs } from 'actions/breadcrumbs';
import style from './style.scss';
class Breadcrumbs extends Component {
constructor(props) {
super(props);
this.state = {
breadcrumbs: [],
names: {}
};
this.setBreadcrumbs = this.setBreadcrumbs.bind(this);
this.loadEntityNameById = this.loadEntityNameById.bind(this);
}
componentWillMount() {
this.setBreadcrumbs();
}
componentWillReceiveProps(nextProps) {
const { isWillUpdate: newIsWillUpdate } = nextProps;
const { isWillUpdate, saveUpdatedBreadcrumbs } = this.props;
if (isWillUpdate === false && newIsWillUpdate === true) {
this.setBreadcrumbs();
saveUpdatedBreadcrumbs();
}
}
setBreadcrumbs() {
const { params, path } = this.props.match;
let currentPath = '';
const pathSplitedAndExtendet = path.split('/')
.filter(item => !!item)
.map(item => {
if (item[0] === ':' && item.slice(1) !== 'adPage') {
const parameterName = item.slice(1);
this.loadEntityNameById(
parameterName,
params[parameterName]
);
return {
route: `/${params[parameterName]}${BREADCRUMBS_ROUTES_FOR_ID[parameterName]}`,
parameter: parameterName
};
}
return {
route: `/${item}`,
parameter: ''
};
});
const breadcrumbs = pathSplitedAndExtendet
.filter(item => item.parameter !== 'adPage')
.map((item) => {
const indexOfRoute = currentPath.indexOf(item.route);
if (currentPath.slice(indexOfRoute) !== item.route) {
currentPath = `${currentPath}${item.route}`;
}
return ({
...item,
name: BREADCRUMBS_ROUTES[item.route] || '',
route: currentPath
});
});
this.setState({ breadcrumbs });
}
async loadEntityNameById(parameter, id) {
const { loadEntityById } = this.props;
await loadEntityById(BREADCRUMBS_ENDPOINT[parameter], id)
.then((data) => {
this.setState({ names: objectAssign(this.state.names, { [parameter]: { id, name: data.name } }) });
});
}
render() {
const { breadcrumbs, names } = this.state;
const { showBreadcrumbs } = this.props;
return (
<div className={style.breadcrumbs}>
{
showBreadcrumbs && breadcrumbs
.map((item, index) => {
return (
<div
key={`${item.name}--${item.route}--${index}`}
className={classnames(style.bread, index === breadcrumbs.length - 1 ? style.last : null)}
role="link"
tabIndex={-10 - index}
onKeyDown={() => {}}
onClick={item.route ? () => this.props.history.push(item.route) : null}
>
{`${item.name || (names[item.parameter]
? names[item.parameter].name : '...')}
${((breadcrumbs.length > 1) && (index !== breadcrumbs.length - 1)) ? ' >' : ''}
`}
</div>
);
})
}
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Breadcrumbs));

Can't call setState (or forceUpdate) 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.
This error message clearly states that your application has memory leak. What's going on here exactly?
Well, you're using async operation (loadEntityNameById) in setBreadcrumbs method. Which is being called in componentWillMount and in componentWillReceiveProps. This means when you go from CampaignPage component to BreadCrumb component, it will do the async operation ie. loadEntityNameById is running in the background which only sets the state once it's finished. But until that time your BreadCrumb component might be unmounted. The react application doesn't allow you to update the state on an unmounted component.
Furthermore, you should not use componentWillMount method at all. Use componentDidMount hook instead.
To fix the issue, what you can do is to set a flag like:
componentDidMount() {
// component is mounted, set the didMount property on BreadCrumb component
// you can use any name instead of didMount what you think is proper
this.didMount = true
// now, you can update the state
if(this.didMount) { // be sure it's not unmounted
this.setState({names: ...})
}
Next, you should clear the didMount property when the component is unmounted.
componentWillUnmount() {
this.didMount = false // component is unmounted
This will ensure you that your application memory will not be leaked. Because, we properly setting the state when required but not when it doesn't require, and stopping unnecessary loop.

You're doing an asynchronous action (loadEntityNameById) that sets the state for the component when it finishes. By that time, your Breadcrumbs component may have been unmounted, and the error is thrown.

You cannot call setState in componentWillMount try using componentDidMount instead

Related

useEffect runs for the old data

I am trying to write a singleton class that will act as a local storage wrapper. I need this because wherever I use localstorage in my whole app, I need each of the items I set to have prefix. This prefix for sure changes, but at only one place, so wrapper seems a good idea so that in my app, I don't have to pass prefix each time I use localStorage.
Here is my wrapper.
let instance;
class LocalStorage {
constructor() {
if(instance){
return instance;
}
instance = this;
instance.cachePrefix = null
}
_getKey(key, usePrefix) {
return usePrefix ? `${this.cachePrefix}:${key}` : key;
}
setPrefix(prefix) {
this.cachePrefix = prefix
}
set(key, value, usePrefix = true) {
if(key == null) {
return
}
localStorage.setItem(this._getKey(key, usePrefix),value)
}
get(key, usePrefix = true) {
return localStorage.getItem(this._getKey(key, usePrefix));
}
}
export const LocalStorageWrapper = new LocalStorage()
Now, where I import this class and call setPrefix, this piece is located in the very parent component tree, so we can say that this setPrefix will be called the first time.
Problem: Even though I call this setPrefix in the very parent, in that very parent, I have async call and when its result gets resolved, that's when I call setPrefix. Even though this is very fast, I am still not sure that this will work all the time.. It's possible that before this async call finishes, child component might start to render and it will try to use wrapper that won't have prefix set up...
I can't use hooks, because the whole react app is written with classes.
I'd appreciate your inputs what can be done here.
UPDATE THIS IS VERY PARENT PROVIDER IN THE TREE.
import React, { useContext, useEffect, useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import BN from 'bn.js'
import { useWallet as useWalletBase, UseWalletProvider } from 'use-wallet'
import { getWeb3, filterBalanceValue } from './web3-utils'
import { useWalletConnectors } from './ethereum-providers/connectors'
import { LocalStorageWrapper } from './local-storage-wrapper'
const NETWORK_TYPE_DEFAULT = 'main'
const WalletContext = React.createContext()
function WalletContextProvider({ children }) {
const {
account,
balance,
ethereum,
connector,
status,
chainId,
providerInfo,
type,
...walletBaseRest
} = useWalletBase()
console.log("========= ", type);
const [walletWeb3, setWalletWeb3] = useState(null)
const [networkType, setNetworkType] = useState(NETWORK_TYPE_DEFAULT)
const connected = useMemo(() => status === 'connected', [status])
// get web3 and networkType whenever chainId changes
useEffect(() => {
let cancel = false
if (!ethereum) {
LocalStorageWrapper.setPrefix(NETWORK_TYPE_DEFAULT)
return
}
const walletWeb3 = getWeb3(ethereum)
setWalletWeb3(walletWeb3)
walletWeb3.eth.net
.getNetworkType()
.then(networkType => {
if (!cancel) {
setNetworkType(networkType)
LocalStorageWrapper.setPrefix(networkType)
}
return null
})
.catch(() => {
setNetworkType(NETWORK_TYPE_DEFAULT)
LocalStorageWrapper.setPrefix(NETWORK_TYPE_DEFAULT)
})
return () => {
cancel = true
setWalletWeb3(null)
setNetworkType(NETWORK_TYPE_DEFAULT)
LocalStorageWrapper.setPrefix(NETWORK_TYPE_DEFAULT)
}
}, [ethereum, chainId])
const wallet = useMemo(
() => ({
account,
balance: new BN(filterBalanceValue(balance)),
ethereum,
networkType: connected ? networkType : 'main',
providerInfo: providerInfo,
web3: walletWeb3,
status,
chainId,
connected,
...walletBaseRest,
}),
[
account,
balance,
ethereum,
networkType,
providerInfo,
status,
chainId,
walletBaseRest,
walletWeb3,
connected,
]
)
return (
<WalletContext.Provider value={wallet}>{children}</WalletContext.Provider>
)
}
WalletContextProvider.propTypes = { children: PropTypes.node }
export function WalletProvider({ children }) {
return (
<UseWalletProvider connectors={useWalletConnectors}>
<WalletContextProvider>{children}</WalletContextProvider>
</UseWalletProvider>
)
}
WalletProvider.propTypes = { children: PropTypes.node }
export function useWallet() {
return useContext(WalletContext)
}

Not able to figure out this error: Can't perform a React state update on an unmounted component [duplicate]

Problem
I am writing an application in React and was unable to avoid a super common pitfall, which is calling setState(...) after componentWillUnmount(...).
I looked very carefully at my code and tried to put some guarding clauses in place, but the problem persisted and I am still observing the warning.
Therefore, I've got two questions:
How do I figure out from the stack trace, which particular component and event handler or lifecycle hook is responsible for the rule violation?
Well, how to fix the problem itself, because my code was written with this pitfall in mind and is already trying to prevent it, but some underlying component's still generating the warning.
Browser 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 the componentWillUnmount
method.
in TextLayerInternal (created by Context.Consumer)
in TextLayer (created by PageInternal) index.js:1446
d/console[e]
index.js:1446
warningWithoutStack
react-dom.development.js:520
warnAboutUpdateOnUnmounted
react-dom.development.js:18238
scheduleWork
react-dom.development.js:19684
enqueueSetState
react-dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_callee$
TextLayer.js:97
tryCatch
runtime.js:63
invoke
runtime.js:282
defineIteratorMethods/</prototype[method]
runtime.js:116
asyncGeneratorStep
asyncToGenerator.js:3
_throw
asyncToGenerator.js:29
Code
Book.tsx
import { throttle } from 'lodash';
import * as React from 'react';
import { AutoWidthPdf } from '../shared/AutoWidthPdf';
import BookCommandPanel from '../shared/BookCommandPanel';
import BookTextPath from '../static/pdf/sde.pdf';
import './Book.css';
const DEFAULT_WIDTH = 140;
class Book extends React.Component {
setDivSizeThrottleable: () => void;
pdfWrapper: HTMLDivElement | null = null;
isComponentMounted: boolean = false;
state = {
hidden: true,
pdfWidth: DEFAULT_WIDTH,
};
constructor(props: any) {
super(props);
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
);
}
componentDidMount = () => {
this.isComponentMounted = true;
this.setDivSizeThrottleable();
window.addEventListener("resize", this.setDivSizeThrottleable);
};
componentWillUnmount = () => {
this.isComponentMounted = false;
window.removeEventListener("resize", this.setDivSizeThrottleable);
};
render = () => (
<div className="Book">
{ this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }
<div className={this.getPdfContentContainerClassName()}>
<BookCommandPanel
bookTextPath={BookTextPath}
/>
<div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
<AutoWidthPdf
file={BookTextPath}
width={this.state.pdfWidth}
onLoadSuccess={(_: any) => this.onDocumentComplete()}
/>
</div>
<BookCommandPanel
bookTextPath={BookTextPath}
/>
</div>
</div>
);
getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';
onDocumentComplete = () => {
try {
this.setState({ hidden: false });
this.setDivSizeThrottleable();
} catch (caughtError) {
console.warn({ caughtError });
}
};
}
export default Book;
AutoWidthPdf.tsx
import * as React from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
interface IProps {
file: string;
width: number;
onLoadSuccess: (pdf: any) => void;
}
export class AutoWidthPdf extends React.Component<IProps> {
render = () => (
<Document
file={this.props.file}
onLoadSuccess={(_: any) => this.props.onLoadSuccess(_)}
>
<Page
pageNumber={1}
width={this.props.width}
/>
</Document>
);
}
Update 1: Cancel throttleable function (still no luck)
const DEFAULT_WIDTH = 140;
class Book extends React.Component {
setDivSizeThrottleable: ((() => void) & Cancelable) | undefined;
pdfWrapper: HTMLDivElement | null = null;
state = {
hidden: true,
pdfWidth: DEFAULT_WIDTH,
};
componentDidMount = () => {
this.setDivSizeThrottleable = throttle(
() => {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
},
500,
);
this.setDivSizeThrottleable();
window.addEventListener("resize", this.setDivSizeThrottleable);
};
componentWillUnmount = () => {
window.removeEventListener("resize", this.setDivSizeThrottleable!);
this.setDivSizeThrottleable!.cancel();
this.setDivSizeThrottleable = undefined;
};
render = () => (
<div className="Book">
{ this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }
<div className={this.getPdfContentContainerClassName()}>
<BookCommandPanel
BookTextPath={BookTextPath}
/>
<div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
<AutoWidthPdf
file={BookTextPath}
width={this.state.pdfWidth}
onLoadSuccess={(_: any) => this.onDocumentComplete()}
/>
</div>
<BookCommandPanel
BookTextPath={BookTextPath}
/>
</div>
</div>
);
getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';
onDocumentComplete = () => {
try {
this.setState({ hidden: false });
this.setDivSizeThrottleable!();
} catch (caughtError) {
console.warn({ caughtError });
}
};
}
export default Book;
Here is a React Hooks specific solution for
Error
Warning: Can't perform a React state update on an unmounted component.
Solution
You can declare let isMounted = true inside useEffect, which will be changed in the cleanup callback, as soon as the component is unmounted. Before state updates, you now check this variable conditionally:
useEffect(() => {
let isMounted = true; // note mutable flag
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []); // adjust dependencies to your needs
const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};
const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useEffect(() => {
let isMounted = true;
fetchData();
return () => {
isMounted = false;
};
// simulate some Web API fetching
function fetchData() {
setTimeout(() => {
// drop "if (isMounted)" to trigger error again
// (take IDE, doesn't work with stack snippet)
if (isMounted) setState("data fetched")
else console.log("aborted setState on unmounted component")
}, 4000);
}
}, []);
return <div>Child: {state}</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
Extension: Custom useAsync Hook
We can encapsulate all the boilerplate into a custom Hook, that automatically aborts async functions in case the component unmounts or dependency values have changed before:
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data);
});
return () => { isActive = false };
}, [asyncFn, onSuccess]);
}
// custom Hook for automatic abortion on unmount or dependency change
// You might add onFailure for promise errors as well.
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data)
else console.log("aborted setState on unmounted component")
});
return () => {
isActive = false;
};
}, [asyncFn, onSuccess]);
}
const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useAsync(simulateFetchData, setState);
return <div>Child: {state}</div>;
};
const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};
const simulateFetchData = () => new Promise(
resolve => setTimeout(() => resolve("data fetched"), 4000));
ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
More on effect cleanups: Overreacted: A Complete Guide to useEffect
To remove - Can't perform a React state update on an unmounted component warning, use componentDidMount method under a condition and make false that condition on componentWillUnmount method. For example : -
class Home extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
news: [],
};
}
componentDidMount() {
this._isMounted = true;
ajaxVar
.get('https://domain')
.then(result => {
if (this._isMounted) {
this.setState({
news: result.data.hits,
});
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
...
}
}
If above solutions dont work, try this and it works for me:
componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}
There is a hook that's fairly common called useIsMounted that solves this problem (for functional components)...
import { useRef, useEffect } from 'react';
export function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
}
then in your functional component
function Book() {
const isMounted = useIsMounted();
...
useEffect(() => {
asyncOperation().then(data => {
if (isMounted.current) { setState(data); }
})
});
...
}
Checking if a component is mounted is actually an anti pattern as per React documentation. The solution to the setState warning is rather to leverage on the use of an AbortController:
useEffect(() => {
const abortController = new AbortController() // creating an AbortController
fetch(url, { signal: abortController.signal }) // passing the signal to the query
.then(data => {
setState(data) // if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return // if the query has been aborted, do nothing
throw error
})
return () => {
abortController.abort() // stop the query by aborting on the AbortController on unmount
}
}, [])
For asynchronous operations that aren't based on the Fetch API, there still should be a way to cancel these asynchronous operations, and you should rather leverage these than just checking if a component is mounted. If you are building your own API, you can implement the AbortController API in it to handle it.
For more context, the check if a component is mounted is an anti pattern as React is checking internally if the component is mounted to display that warning. Doing the same check again is just a way to hide the warning, and there are some easier ways to hide them than adding this piece of code on a big part of a codebase.
Source: https://medium.com/doctolib/react-stop-checking-if-your-component-is-mounted-3bb2568a4934
I had this warning possibly because of calling setState from an effect hook (This is discussed in these 3 issues linked together).
Anyway, upgrading the react version removed the warning.
React already removed this warning
but here is a better solution (not just workaround)
useEffect(() => {
const abortController = new AbortController() // creating an AbortController
fetch(url, { signal: abortController.signal }) // passing the signal to the query
.then(data => {
setState(data) // if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return // if the query has been aborted, do nothing
throw error
})
return () => {
abortController.abort()
}
}, [])
The solution from #ford04 didn't worked to me and specially if you need to use the isMounted in multiple places (multiple useEffect for instance), it's recommended to useRef, as bellow:
Essential packages
"dependencies":
{
"react": "17.0.1",
}
"devDependencies": {
"typescript": "4.1.5",
}
My Hook Component
export const SubscriptionsView: React.FC = () => {
const [data, setData] = useState<Subscription[]>();
const isMounted = React.useRef(true);
React.useEffect(() => {
if (isMounted.current) {
// fetch data
// setData (fetch result)
return () => {
isMounted.current = false;
};
}
}
});
try changing setDivSizeThrottleable to
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
{ leading: false, trailing: true }
);
I know that you're not using history, but in my case I was using the useHistory hook from React Router DOM, which unmounts the component before the state is persisted in my React Context Provider.
To fix this problem I have used the hook withRouter nesting the component, in my case export default withRouter(Login), and inside the component const Login = props => { ...; props.history.push("/dashboard"); .... I have also removed the other props.history.push from the component, e.g, if(authorization.token) return props.history.push('/dashboard') because this causes a loop, because the authorization state.
An alternative to push a new item to history.
Add a ref to a jsx component and then check it exist
function Book() {
const ref = useRef();
useEffect(() => {
asyncOperation().then(data => {
if (ref.current) setState(data);
})
});
return <div ref={ref}>content</div>
}
I had a similar issue thanks #ford04 helped me out.
However, another error occurred.
NB. I am using ReactJS hooks
ndex.js:1 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
What causes the error?
import {useHistory} from 'react-router-dom'
const History = useHistory()
if (true) {
history.push('/new-route');
}
return (
<>
<render component />
</>
)
This could not work because despite you are redirecting to new page all state and props are being manipulated on the dom or simply rendering to the previous page did not stop.
What solution I found
import {Redirect} from 'react-router-dom'
if (true) {
return <redirect to="/new-route" />
}
return (
<>
<render component />
</>
)
If you are fetching data from axios and the error still occurs, just wrap the setter inside the condition
let isRendered = useRef(false);
useEffect(() => {
isRendered = true;
axios
.get("/sample/api")
.then(res => {
if (isRendered) {
setState(res.data);
}
return null;
})
.catch(err => console.log(err));
return () => {
isRendered = false;
};
}, []);
I have 2 solutions for this error:
return:
If you are used hook and useEffect, So put a return end of useEffect.
useEffect(() => {
window.addEventListener('mousemove', logMouseMove)
return () => {
window.removeEventListener('mousemove', logMouseMove)
}
}, [])
componentWillUnmount:
If you are used componentDidMount, So put componentWillUnmount next to it.
componentDidMount() {
window.addEventListener('mousemove', this.logMouseMove)
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.logMouseMove)
}
The isMounted approach is an anti-pattern in most cases because it doesn't actually clean up/cancel anything, it just avoids changing state on unmounted components, but does nothing with pending asynchronous tasks. The React team recently removed the leak warning because users keep creating a lot of anti-patterns to hide the warning rather than fix its cause.
But writing cancellable code in plain JS can be really tricky. To fix this I made my own lib useAsyncEffect2 with custom hooks, built on top of a cancellable promise (c-promise2) for executing cancellable async code to reach its graceful cancellation. All async stages (promises), including deep ones, are cancellable. This means that the request here will be automatically aborted if its parent context is canceled. Of course, any other asynchronous operation can be used instead of a request.
useAsyncEffect Demo with plain useState usage (Live Demo):
import React, { useState } from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
function TestComponent({url}) {
const [text, setText] = useState("");
const cancel = useAsyncEffect(
function* () {
setText("fetching...");
const json = (yield cpAxios(url)).data;
setText(`Success: ${JSON.stringify(json)}`);
},
[url]
);
return (
<div>
<div>{text}</div>
<button onClick={cancel}>
Cancel request
</button>
</div>
);
}
useAsyncEffect Demo with internal states usage (Live Demo):
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
function TestComponent({ url, timeout }) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
return (yield cpAxios(url).timeout(timeout)).data;
},
{ states: true, deps: [url] }
);
return (
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
<button onClick={cancel} disabled={done}>
Cancel async effect (abort request)
</button>
</div>
);
}
Class component using decorators (Live demo)
import React, { Component } from "react";
import { ReactComponent } from "c-promise2";
import cpAxios from "cp-axios";
#ReactComponent
class TestComponent extends Component {
state = {
text: ""
};
*componentDidMount(scope) {
const { url, timeout } = this.props;
const response = yield cpAxios(url).timeout(timeout);
this.setState({ text: JSON.stringify(response.data, null, 2) });
}
render() {
return (<div>{this.state.text}</div>);
}
}
export default TestComponent;
More other examples:
Axios request with errors handling
Fetch weather by coords
Live search
Pause & Resume
Progress capturing
Edit: I just realized the warning is referencing a component called TextLayerInternal. That's likely where your bug is. The rest of this is still relevant, but it might not fix your problem.
1) Getting the instance of a component for this warning is tough. It looks like there is some discussion to improve this in React but there currently is no easy way to do it. The reason it hasn't been built yet, I suspect, is likely because components are expected to be written in such a way that setState after unmount isn't possible no matter what the state of the component is. The problem, as far as the React team is concerned, is always in the Component code and not the Component instance, which is why you get the Component Type name.
That answer might be unsatisfactory, but I think I can fix your problem.
2) Lodashes throttled function has a cancel method. Call cancel in componentWillUnmount and ditch the isComponentMounted. Canceling is more "idiomatically" React than introducing a new property.
UPDATE DO NOT USE MY ORIGINAL ANSWER AS IT DOES NOT WORK
This answer was based on the use of cancelable promises and a note in makecancelable which I migrated to use hooks. However, it appears it does not cancel a chain of async/await and even cancelable-promise does not support canceling of a chain of awaits
Doing a bit more research on this, it appears that some internal Google reasons prevented cancelable promises from coming into the standard.
Further more, there was some promise with Bluebird which introduces cancelable promises, but it does not work in Expo or at least I haven't seen an example of it working in Expo.
The accepted answer is the best. Since I use TypeScript I had adapted the code with a few modifications (I explicitly set the dependencies since the accepted answer's implicit dependencies appear to give a re-render loop on my app, added and use async/await rather than promise chain, pass a ref to the mounted object so that an async/await chain can be canceled earlier if needed)
/**
* This starts an async function and executes another function that performs
* React state changes if the component is still mounted after the async
* operation completes
* #template T
* #param {(mountedRef: React.MutableRefObject<boolean>) => Promise<T>} asyncFunction async function,
* it has a copy of the mounted ref so an await chain can be canceled earlier.
* #param {(asyncResult: T) => void} onSuccess this gets executed after async
* function is resolved and the component is still mounted
* #param {import("react").DependencyList} deps
*/
export function useAsyncSetEffect(asyncFunction, onSuccess, deps) {
const mountedRef = useRef(false);
useEffect(() => {
mountedRef.current = true;
(async () => {
const x = await asyncFunction(mountedRef);
if (mountedRef.current) {
onSuccess(x);
}
})();
return () => {
mountedRef.current = false;
};
}, deps);
}
Original answer
Since I have many different operations that are async, I use the cancelable-promise package to resolve this issue with minimal code changes.
Previous code:
useEffect(() =>
(async () => {
const bar = await fooAsync();
setSomeState(bar);
})(),
[]
);
New code:
import { cancelable } from "cancelable-promise";
...
useEffect(
() => {
const cancelablePromise = cancelable(async () => {
const bar = await fooAsync();
setSomeState(bar);
})
return () => cancelablePromise.cancel();
},
[]
);
You can alsowrpte it in a custom utility function like this
/**
* This wraps an async function in a cancelable promise
* #param {() => PromiseLike<void>} asyncFunction
* #param {React.DependencyList} deps
*/
export function useCancelableEffect(asyncFunction, deps) {
useEffect(() => {
const cancelablePromise = cancelable(asyncFunction());
return () => cancelablePromise.cancel();
}, deps);
}
In my case of a login-like screen, the fetch was done in a onClick handler of a parent component, who passed that handler down to the child, whom placed .catch and .finally on it.
In the .then case a redirect (and hence unmount) would happen as normal operation, and only in cases of fetch error would the child stay mounted on-screen.
My solution was moving the setState and all other code from the .finally to the .catch since the child is guaranteed to be mounted in the .catch case. And in the .then case nothing needed doing because of the guaranteed unmount.
Based on #ford04 answer, here is the same encapsulated in a method :
import React, { FC, useState, useEffect, DependencyList } from 'react';
export function useEffectAsync( effectAsyncFun : ( isMounted: () => boolean ) => unknown, deps?: DependencyList ) {
useEffect( () => {
let isMounted = true;
const _unused = effectAsyncFun( () => isMounted );
return () => { isMounted = false; };
}, deps );
}
Usage:
const MyComponent : FC<{}> = (props) => {
const [ asyncProp , setAsyncProp ] = useState( '' ) ;
useEffectAsync( async ( isMounted ) =>
{
const someAsyncProp = await ... ;
if ( isMounted() )
setAsyncProp( someAsyncProp ) ;
});
return <div> ... ;
} ;
Depending on how you open your webpage, you may not be causing a mounting. Such as using a <Link/> back to a page that was already mounted in the virtual DOM, so requiring data from a componentDidMount lifecycle is caught.
Here is a simple solution for this. This warning is due to when we do some fetch request while that request is in the background (because some requests take some time.)and we navigate back from that screen then they react cannot update the state. here is the example code for this. write this line before every state Update.
if(!isScreenMounted.current) return;
Here is the Complete Code
import React , {useRef} from 'react'
import { Text,StatusBar,SafeAreaView,ScrollView, StyleSheet } from 'react-native'
import BASEURL from '../constants/BaseURL';
const SearchScreen = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
return () => isScreenMounted.current = false
},[])
const ConvertFileSubmit = () => {
if(!isScreenMounted.current) return;
setUpLoading(true)
var formdata = new FormData();
var file = {
uri: `file://${route.params.selectedfiles[0].uri}`,
type:`${route.params.selectedfiles[0].minetype}`,
name:`${route.params.selectedfiles[0].displayname}`,
};
formdata.append("file",file);
fetch(`${BASEURL}/UploadFile`, {
method: 'POST',
body: formdata,
redirect: 'manual'
}).then(response => response.json())
.then(result => {
if(!isScreenMounted.current) return;
setUpLoading(false)
}).catch(error => {
console.log('error', error)
});
}
return(
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Text>Search Screen</Text>
</ScrollView>
</SafeAreaView>
</>
)
}
export default SearchScreen;
const styles = StyleSheet.create({
scrollView: {
backgroundColor:"red",
},
container:{
flex:1,
justifyContent:"center",
alignItems:"center"
}
})
I solved this problem by providing all the params that are used in the useEffect hook
The code reported the bug:
useEffect(() => {
getDistrict({
geonameid: countryId,
subdistrict: level,
}).then((res) => {
......
});
}, [countryId]);
The code after fix:
useEffect(() => {
getDistrict({
geonameid: countryId,
subdistrict: level,
}).then((res) => {
......
});
}, [countryId,level]);
Can see that , problems solved after I provided all the params(including the level param) that supposed to pass through.
I had a similar problem and solved it :
I was automatically making the user logged-in by dispatching an action on redux
( placing authentication token on redux state )
and then I was trying to show a message with this.setState({succ_message: "...")
in my component.
Component was looking empty with the same error on console : "unmounted component".."memory leak" etc.
After I read Walter's answer up in this thread
I've noticed that in the Routing table of my application ,
my component's route wasn't valid if user is logged-in :
{!this.props.user.token &&
<div>
<Route path="/register/:type" exact component={MyComp} />
</div>
}
I made the Route visible whether the token exists or not.
In my case the issue was that the parent component was hidding the child because of a condition change in the child component.
So what I did was to change the condition so the child component was always shown.
What was happening:
const ParentComponent:FC = () => {
...
if (someCondition) {
return null;
}
return (
<>
Some cool text here
<ChildModalComponent message="this is a cool modal" />
</>
)
}
const ChildModalComponent: FC = () => {
...
const handleSubmit = () => {
setSomeCondition(true);
}
}
So after clicking submit the modal was automatically hidden becasue of the parent condition (someCondition).
How did I fix it?
I changed the place where the someCondition was checked in the Parent component, so the child component was always shown:
const ParentComponent:FC = () => {
...
return (
<>
{!someCondition && <>Some cool text here</>
<ChildModalComponent message="this is a cool modal" />
</>
)
}
I faced same warning, not it is fixed. To fix the issue, I removed the useRef() variable check in useEffect()
Earlier, the code was
const varRef = useRef();
useEffect(() => {
if (!varRef.current)
{
}
}, []);
Now, the code is
const varRef = useRef();
useEffect(() => {
//if (!varRef.current)
{
}
}, [])
Hope, it helps...
Inspired by the accepted answer by #ford04 I had even better approach dealing with it, instead of using useEffect inside useAsync create a new function that returns a callback for componentWillUnmount :
function asyncRequest(asyncRequest, onSuccess, onError, onComplete) {
let isMounted=true
asyncRequest().then((data => isMounted ? onSuccess(data):null)).catch(onError).finally(onComplete)
return () => {isMounted=false}
}
...
useEffect(()=>{
return asyncRequest(()=>someAsyncTask(arg), response=> {
setSomeState(response)
},onError, onComplete)
},[])
const handleClick = async (item: NavheadersType, index: number) => {
const newNavHeaders = [...navheaders];
if (item.url) {
await router.push(item.url); =>>>> line causing error (causing route to happen)
// router.push(item.url); =>>> coreect line
newNavHeaders.forEach((item) => (item.active = false));
newNavHeaders[index].active = true;
setnavheaders([...newNavHeaders]);
}
};
The simplest and most compact solution (with an explanation) is seen below as a one-liner solution.
useEffect(() => { return () => {}; }, []);
The useEffect() example above returns a callback function triggers React to finish its unmount portion of its life-cycle prior to updating state.
That very simplistic solution is all that is needed. In addition, it also works unlike the fictional syntax provided by #ford04 and #sfletche . By the way, the below code snippet from #ford04 is purely imaginary syntax (#sfletche , #vinod , #guneetgstar , and #Drew Cordano used the very same imaginary syntax).
data => {       <--- Fictional/Imaginary Syntax
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
All of my linters and all the linters of my entire team will not accept it and they report Uncaught SyntaxError: unexpected token: '=>'. I am surprised that no one caught the imaginary syntax. Would anyone who has participated in this question-thread, particularly among the up-voters, explain to me how they got the solutions to work for them?
Inspired by #ford04 answer I use this hook, which also takes callbacks for success, errors, finally and an abortFn:
export const useAsync = (
asyncFn,
onSuccess = false,
onError = false,
onFinally = false,
abortFn = false
) => {
useEffect(() => {
let isMounted = true;
const run = async () => {
try{
let data = await asyncFn()
if (isMounted && onSuccess) onSuccess(data)
} catch(error) {
if (isMounted && onError) onSuccess(error)
} finally {
if (isMounted && onFinally) onFinally()
}
}
run()
return () => {
if(abortFn) abortFn()
isMounted = false
};
}, [asyncFn, onSuccess])
}
If the asyncFn is doing some kind of fetch from back-end it often makes sense to abort it when the component is unmounted (not always though, sometimes if ie. you're loading some data into a store you might as well just want to finish it even if component is unmounted)

React memory leak - when updating state in context provider via a function passed to a child of the provider

After some debugging I understand the issue and I know roughly why it's happening, so I will show as much code as I can.
The Error
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 ProductsDisplay (created by ConnectFunction)
in ConnectFunction (created by Context.Consumer)
in Route (created by SiteRouter)
in Switch (created by SiteRouter)
in SiteRouter (created by ConnectFunction)
in ConnectFunction (created by TLORouter)
in Route (created by TLORouter)
in Switch (created by TLORouter)
So to give you context, the React structure looks a bit like so
Simplified version
App.jsx > Router > GlobalLayoutProvider > Route > Page
Within the GlobalLayoutProvider I pass six functions down via the new react context, the code looks like so. All these functions provide is the ability to modify the state of the layout component, so that if child elements have more complex requirements they can send the information up after performing fetchs etc or they could on mount set the values of the layout.
GlobalLayoutRedux.jsx
class GlobalLayoutProvider extends React.Component {
constructor(props) {
super(props);
this.state = { routeConfig: null };
this.getRouteData = this.getRouteData.bind(this);
this.setLoaderOptions = this.setLoaderOptions.bind(this);
}
componentDidMount() {
this.getRouteData();
}
componentDidUpdate(prevProps) {
const { urlParams, user, layoutSettings } = this.props;
if (
urlParams.pathname !== prevProps.urlParams.pathname
|| user.permissions !== prevProps.user.permissions
) {
this.getRouteData();
}
}
getRouteData() {
const { user, urlParams } = this.props;
const { tlo, site, pathname } = urlParams;
this.setState({
routeConfig: pageConfigs().find(
(c) => c.pageContext(tlo, site, user) === pathname,
),
});
}
setLoaderOptions(data) {
this.setState((prevState) => ({
routeConfig: {
...prevState.routeConfig,
loader: {
display: data?.display || initialState.loader.display,
message: data?.message || initialState.loader.message,
},
},
}));
}
render() {
const { routeConfig } = this.state;
const { children, user } = this.props;
return (
<GlobalLayoutContext.Provider
value={{
setLoaderOptions: this.setLoaderOptions,
}}
>
<PageContainer
title={routeConfig?.pageContainer?.title}
breadcrumbs={[routeConfig?.pageContainer?.title]}
>
<ActionsBar
actionsBarProperties={{ actions: routeConfig?.actionBar?.actions }}
pageTitle={routeConfig?.actionBar?.title}
/>
<SideNav items={routeConfig?.sideNav?.options} selected={routeConfig?.sideNav?.pageNavKey}>
<div id={routeConfig?.sideNav?.pageNavKey} className="Content__body page-margin">
<div id="loader-instance" className={`${routeConfig?.loader?.display ? '' : 'd-none'}`}>
<Loader message={routeConfig?.loader?.message} />
</div>
<div id="children-instance" className={`${routeConfig?.loader?.display ? 'd-none' : ''}`}>
{children}
</div>
</div>
</SideNav>
</PageContainer>
</GlobalLayoutContext.Provider>
);
}
}
export default GlobalLayoutProvider;
Inside the Page.jsx we have a componentDidMount and a componentDidUpdate. The issue seems to stem from calling the parent function and setting the state pretty much at any point prior to updating the state of the child component.
Page.jsx
export default class Page extends Component {
static contextType = GlobalLayoutContext;
constructor(props) {
super(props);
this.state = {
someState: 'stuff'
};
}
componentDidMount() {
this.setActionBarButtons();
this.fetchOrganisationsProducts();
}
async componentDidUpdate(prevProps) {
const { shouldProductsRefresh, selectedOrganisation, permissions } = this.props;
if (
selectedOrganisation?.id !== prevProps.selectedOrganisation?.id
|| shouldProductsRefresh !== prevProps.shouldProductsRefresh
) {
await this.fetchOrganisationsProducts();
}
if (
selectedOrganisation?.id !== prevProps.selectedOrganisation?.id
|| shouldProductsRefresh !== prevProps.shouldProductsRefresh
|| permissions !== prevProps.permissions
) {
this.setActionBarButtons();
}
}
setActionBarButtons() {
const { setActionBarOptions } = this.context;
const actions = [
ActionButtons.Custom(
() => this.setState({ exportTemplateModalIsOpen: true }),
{ title: 'Button', icon: 'button' },
),
];
setActionBarOptions({ actions, title: 'Products', display: true });
}
async fetchOrganisationsProducts() {
const { selectedOrganisation } = this.props;
const { setLoaderOptions } = this.context;
setLoaderOptions({ display: true, message: 'Loading Products In Organisation' });
(await productStoreService.getProducts(selectedOrganisation.id))
.handleError(() => setLoaderOptions({ display: false }))
.handleOk((products) => {
this.setState({ products }, () => {
setLoaderOptions({ display: false });
products.forEach(this.fetchAdditionalInformation)
});
});
}
render() {
return (<p>Something</p>)
}
}
What's odd the memory leak will disappear if I add this suggestion I seen on stack overflow suggesting to track the state of the components interacting with the higher-level component.
export default class Page extends Component {
static contextType = GlobalLayoutContext;
constructor(props) {
super(props);
this.state = {
someState: 'stuff'
};
}
// ADDITION HERE
_isMounted = false;
componentDidMount() {
// ADDITION HERE
this._isMounted = true;
this.setActionBarButtons();
this.fetchOrganisationsProducts();
}
// ADDITION HERE
componentWillUnmount() {
this._isMounted = false;
}
async fetchOrganisationsProducts() {
const { selectedOrganisation } = this.props;
const { setLoaderOptions } = this.context;
setLoaderOptions({ display: true, message: 'Loading Products In Organisation' });
(await productStoreService.getProducts(selectedOrganisation.id))
.handleError(() => setLoaderOptions({ display: false }))
.handleOk((products) => {
// ADDITION HERE
if (this._isMounted) {
this.setState({ products }, () => {
setLoaderOptions({ display: false });
products.forEach(this.fetchAdditionalInformation)
});
}
});
}
render() {
return (<p>Something</p>)
}
}
Personally, I don't see this as a solution if I was building my own thing I wouldn't be too fussed but I can't ask an entire company to start adding this addition everywhere.
My gut is telling me that because the component is firing up an object to configure the state of the parent which is for a fraction of a second unmounting as the component did mount is still processing due to the async network fetch when that is returned it is saving to the state before the parent has managed to render the function call state change.
What was odd if I pass the callbacks into the parent and call them once the setState has been actioned the issue is resolved like so
setOnMountOptions(data) {
this.setState((prevState) => ({
routeConfig: {
...prevState.routeConfig,
...data?.loader ? { loader: data.loader } : {},
},
}), async () => { await data.callbacks(); });
}
but again this causes havoc on the testing side as you are abstracting the componentDidmount functionality out and calling it after a set state is actioned elsewhere.
I have tried adapting what I have to Redux but I had the exact same result everything from a viewing perspective in the browser was fine but still getting the same memory leak using the Redux calls to try to populate all the data from top to bottom.
I can't think of any way of handling this gracefully where we don't need to ask the company to add that fix everywhere.
So to save people time and effort it turns out our memory leak was actually being cause by a bad set state in the routers of our application.

why updated state not reflected inside an event listener: React Native, Hooks

I'm using hooks for updating state. in my code I have an AppState event listener and whenever it triggers I'm updating the appState using setAppState, however the appState inside the event listener did not change. but the value is updating outside the listener. can anyone explain why is behaving like that?
Here is my code:
import React, { FunctionComponent, useEffect, useState } from "react"
import { View, AppState, AppStateStatus } from "react-native"
const Test: FunctionComponent<any> = (props: any) => {
const [appState, setAppState] = useState < AppStateStatus > (AppState.currentState)
useEffect(() => {
AppState.addEventListener("change", _handleAppStateChange)
},[])
const _handleAppStateChange = (nextAppState: AppStateStatus) => {
//appState not Changing here
console.log(appState, "app state")
if (appState.match(/inactive|background/) && nextAppState === "active") {
console.log("App has come to the foreground!")
activateRealtime()
} else if (appState === "active" && nextAppState.match(/inactive|background/)) {
console.log("App has come to background!")
}
setAppState(nextAppState)
}
//appState updated here
console.log(appState, "app state")
return <View />
}
In your code appState is a stale closure the linter should have told you that you have missing dependencies.
I think the following will work
const _handleAppStateChange = useCallback(
(nextAppState) =>
//use callback for state setter so you don't
// create needless dependency or (as you did)
// create a stale closure
setAppState((currentAppState) => {
//logs current appstate
console.log(currentAppState, 'app state');
if (
currentAppState.match(/inactive|background/) &&
nextAppState === 'active'
) {
console.log('App has come to the foreground!');
activateRealtime();
} else if (
currentAppState === 'active' &&
nextAppState.match(/inactive|background/)
) {
console.log('App has come to background!');
}
return nextAppState;
}),
//only pass function as _handleAppStateChange
// on mount by providing empty dependency
[]
);
useEffect(() => {
AppState.addEventListener(
'change',
_handleAppStateChange
);
//clean up code when component unmounts
return () =>
AppState.removeEventListener(
'change',
_handleAppStateChange
);
//_handleAppStateChange is a dependency but useCallback
// has empty dependency so it is only created on mount
}, [_handleAppStateChange]);

Why is this function call in render() creating an infinite loop?

I want to call a function in render() which will update the state. But when I do that, it gives me this error message:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I don't understand why this is happening because I'm not directly setting the state in my render(), I'm setting it in my setInsightUrl() function.
I've tried using different lifecycle functions but couldn't get any to work. I'm not sure how else to write this function.
import React, { Component } from 'react'
import "../../css/tabs/Tabs.css"
import {connect} from "react-redux"
class InsightPage extends Component {
constructor() {
super();
this.state = {
insightUrlState: null
}
this.setInsightUrl = this.setInsightUrl.bind(this);
}
setInsightUrl(url) {
this.setState({
insightUrlState: url
})
console.log(this.state.insightUrlState, 'INSIGHTTTTTT URLLLLLbgnhjm,k.l')
}
render() {
this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === this.props.insightNameReducer) {
{this.setInsightUrl(insight.insightURL)}
}
})
})
})
return (
<div className={this.props.drawerOpen ? "tab_container2" : "tab_container" }>
<h1>Hello from Insight</h1>
<iframe frameBorder="0" style={{width: "100%", height: "70vh"}} src="https://insighttbdashboards.verizon.com/t/DigtalAnalytics/views/Digital_Analytics/Chat_Dashboard?iframeSizedToWindow=true&:embed=y&:showAppBanner=false&:display_count=no&:showVizHome=no#2" />
</div>
)
}
}
const mapStateToProps = state => ({
drawerOpen: state.SideDrawerReducer.open,
sideTreeMenu: state.SideDrawerReducer.menu,
insightNameReducer: state.SideDrawerReducer.insightName
})
export default connect(mapStateToProps)(InsightPage);
It should update the state with the url I am passing into the function in the render block.
Just because you are calling setState in a function defined outside of render (setInsightUrl) doesn't mean you aren't calling it within render, render potentially calls setInsightUrl when the right conditions are met, and thus can potentially loop forever.
Perhaps you could update the state only if it actually is changing:
setInsightUrl(url) {
if (this.state.insightUrlState != url) {
this.setState({
insightUrlState: url
})
console.log(this.state.insightUrlState, 'INSIGHTTTTTT URLLLLLbgnhjm,k.l')
}
}
From the code you posted (I'm not sure if that is the full code for your component) there's no need to determine the insight url in the render() function. If you do want to determine it in the render function (which should be the last thing your component does) then you shouldn't need to put it in the state, you should just use a local variable for it.
But if you want it in the state, you can either do it in the constructor:
constructor(props) {
super(props);
let insightUrlState = null;
props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === props.insightNameReducer) {
insightUrlState = insight.insightURL;
}
});
});
});
this.state = { insightUrlState };
}
With an additional use of a lifecycle method if you want to update the state when the props change:
componentDidUpdate(prevProps, prevState) {
// depending on how many items are in these arrays, you might want to
// wrap this in a check to see if this.props.sideTreeMenu.dynamicMenu has
// changed from prevProps.sideTreeMenu.dynamicMenu and/or if
// this.props.insightNameReducer has changed from prevProps.insightNameReducer
let insightUrlState = null;
this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === this.props.insightNameReducer) {
insightUrlState = insight.insightURL;
}
});
});
});
if (prevState.insightUrlState !== insightUrlState) {
this.setState({ insightUrlState });
}
}
Or, alternatively, you can use the getDerivedStateFromProps function to determine the insightUrlState value just before rendering (using this function, you don't need to use the constructor or componentDidUpdate options):
static getDerivedStateFromProps(props) {
let insightUrlState = null;
props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === props.insightNameReducer) {
insightUrlState = insight.insightURL;
}
});
});
});
return { insightUrlState };
}
this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === this.props.insightNameReducer) {
{this.setInsightUrl(insight.insightURL)}
}
})
})
})
This block is not valid JSX, you might need to move that to componentDidMount.
You can't call setState inside render, otherwise will cause a re-render, so it will go again to render and so on... That's why you got that error.

Categories

Resources