Keep getting the following error message in React Native, really don't understand where it is coming from
Warning: 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.
I have the following simple component:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoggedIn: false,
}
}
componentDidMount(){
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
this.setState({ isLoggedIn: true })
}
}
render() {
const login = this.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
You can use it:
componentDidMount() {
this.function()
}
function = async () => {
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
this.setState({ isLoggedIn: true })
}
}
Or you can call function in constructor.
I hope this will help you...
It's will be work:
let self;
class App extends React.Component {
constructor(props) {
super(props)
self = this;
this.state = {
isLoggedIn: false,
}
}
componentDidMount(){
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
self.setState({ isLoggedIn: true })
}
}
render() {
const login = self.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
you need use isMounted variable.
componentDidMount(){
this.setState({ isMounted = true });
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null && this.isMounted) {
this.setState({ isLoggedIn: true })
}
}
componentWillUnmount(){
this.setState({ isMounted = false });
}
Or if you use Axios, you can use cancel request feature of axios
this here: https://github.com/axios/axios#cancellation
You can try this:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoggedIn: false,
}
}
_isMounted = false;
componentDidMount(){
this._isMounted = true;
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null && this._isMounted) {
this.setState({ isLoggedIn: true })
}
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const login = this.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
By using _isMounted, setState is called only if component is mounted, The unmounting doesn't wait for the async call to finish. Eventually when the async call gets over, the component is already unmounted and so it cannot set the state. To do this, the answer simply does a check to see if the component is mounted before setting the state.
Cancel all the async operation is one of the solution
For me, I resolved it by restart the server by "yarn start" or "npm start"
Related
What I want to do
When a child component first rendering, I would like to use the value in props from a parent component
Problem
When a child component is first rendered, props is not set to state in the child component
I am a beginner to React. I am trying to use props in order to call API by axios in componentDidMount in a child component. I mean, what I am doing is calling API in a parent component and setting data from this API to a child component as props.
However, when I try to do that, props is not set to state in a child component.
For example, when I retrieve product which has some category, I type localhost:3000/api/parent/?category=1/. But, my console.log shows me localhost:3000/api/parent/?category=undefined because I guess props is not set when a child component first rendering.
Actually, I can see category object in state like below.
I guess props is completely set to state after the child component finish first rendering.
How could I set props which comes from API to state?
Although I tried many solutions I found on the stackoverflow, I got stuck at this problem so long time.
I would like you to tell me some solutions.
Thank you very much.
== == ==
My code is like this.
Parent Component
class Top extends Component {
constructor(props) {
super(props);
this.state = {
loginUser: '',
categories: [],
};
}
async componentDidMount() {
const localhostUrl = 'http://localhost:8000/api/';
const topCategoryList = ['Smartphone', 'Tablet', 'Laptop'];
let passCategoryToState=[]
axios
.get(localhostUrl + 'user/' + localStorage.getItem('uid'))
.then((res) => {
this.setState({ loginUser: res.data });
})
.catch((err) => console.log(err));
await Promise.all(
topCategoryList.map(async (category) => {
await axios.get(localhostUrl + 'category/?name=' + category).then((res) => {
passCategoryToState=[...passCategoryToState, res.data]
console.log(passCategoryToState);
});
})
);
this.setState({categories : passCategoryToState})
}
render() {
if (!this.props.isAuthenticated) {
return <p> Developing now </p>;
}
if (this.state.loginUser === '' ) {
return <CircularProgress />;
} else {
return (
<>
<Header loginUser={this.state.loginUser} />
<Give_Item_List
axiosUrl="http://localhost:8000/api/"
subtitle="Smartphone Items"
loginUser={this.state.loginUser}
category={this.state.categories[0]}
/>
</>
);
}
}
}
export default Top;
And, child component
class Give_Item_List extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
loginUser: this.props.loginUser,
category: this.props.category,
};
}
async componentDidMount() {
let pickedGiveItems;
await this.setState({ loading: true });
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.state.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
render() {
if (this.state.loading == true) {
return <CircularProgress />;
}
return <h1>Give_Item_List</h1>;
}
}
export default Give_Item_List;
==============
Edit:Change to componentDidUpdate
componentDidUpdate(prevProps) {
if(prevProps.category != this.props.category){
let pickedGiveItems;
this.setState({ loading: true });
axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('NotFount'));
this.setState({ loading: false });
}
}
It still doesn't work...
In the constructor, this.props is undefined, but you can access the props directly.
constructor(props) {
super(props);
this.state = {
loading: false,
loginUser: props.loginUser,
category: props.category,
};
}
However, I should note now that storing props in state is a common react anti-pattern, you should always consume prop values from this.props where you need them.
For example:
async componentDidMount() {
let pickedGiveItems;
await this.setState({ loading: true });
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
You also can't await a react state update since it isn't an async function nor does it return a Promise. Just provide an initial true loading state and toggle false when the fetch request resolves.
class Give_Item_List extends Component {
constructor(props) {
super(props);
this.state = {
loading: true
};
}
async componentDidMount() {
let pickedGiveItems;
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
render() {
if (this.state.loading) {
return <CircularProgress />;
}
return <h1>Give_Item_List</h1>;
}
}
I'm creating a block for Wordpress with Gutenberg Editor, which is working on React js.
So I'm calling Wordpress API by apiFetch(), which is same to fetch():
class PortfolioTagsEdit extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
};
}
componentDidMount() {
const { attributes } = this.props;
const { switcher } = attributes;
this.setState({ isLoading: true });
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => this.setState({ data, isLoading: false }));
}
...
}
For variable switcher I have controllers which are changing the value.
My problem is when I switch the value of switcher I should reload api call, but I don't know how)
Can you help me, please?
Using react hooks you can use useEffect for fetching API.
function PortfolioTagsEdit({ attributes }) {
// state
const [loading, setLoading] = useState(false);
const [data, setData] = useState([])
// here useEffect will run on component mount and every-time attributes.switcher changes
useEffect(() => {
setLoading(true)
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => {
setLoading(false)
setData(data)
});
}, [attributes.switcher])
return (
....
)
}
The easiest way to do this would be to have the switcher variable in state. You can then implement the componentDidUpdate method to call your apiFetch:
class PortfolioTagsEdit extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
switcher: this.props.attributes.switcher
};
}
componentDidMount() {
this.callAPI()
}
componentDidUpdate(prevProps, prevState) {
if (prevState.switcher !== this.state.switcher) this.callAPI();
}
callAPI() {
const { switcher } = this.state;
this.setState({ isLoading: true });
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => this.setState({ data, isLoading: false }));
}
...
}
Check out the docs for componentDidUpdate - https://reactjs.org/docs/react-component.html#componentdidupdate
You could also take a look on how to do this using hooks, specifically useEffect -https://reactjs.org/docs/hooks-reference.html#useeffect
I am consoling state right after my function call in componentDidMount but it's giving data as EMPTY String.
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: ""
};
}
getData = () => {
functionApiCall().then(res => {
this.setState({
data: res.data
}); // Here the state is getting set
})
}
componentDidMount() {
this.getData();
console.log(this.state.data); //Empty string
}
render() {
return <></>;
}
}
export default App;
Any help will be appreciated.Thank you
Well, I think the api call is returning null , maybe change it like this
getData = () => {
functionApiCall().then(res => {
if(res && res.data) {
this.setState({
data: res.data
})// Here the state is getting set
}
}
}
Above should be fine, but just in case try this
getData = () => {
return new Promise(function(resolve, reject) {
functionApiCall().then(res => {
if(res && res.data) {
this.setState({
data: res.data
}, () => { resolve(res.data) })// Here the state is getting set
}
} });
}
And componentDidMount wait for your promise which resolves after state is set
async componentDidMount(){
await this.getData();
console.log(this.state.data) //NULL
}
setState is asynchronous so you cannot immediately access it.
You can render conditionally like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
getData = () => {
functionApiCall().then(res => {
this.setState({
data: res.data
});
});
};
componentDidMount() {
this.getData();
}
render() {
if (!this.state.data) {
return <div>Loading...</div>;
} else {
return <div>Data: {JSON.stringify(this.state.data)}</div>;
}
}
}
export default App;
Sample codesandbox with a fake api
Where should I set the state of the component with the information gotten from a resolved promise in a way that if the component is unmounted before the last two actions are done it doesn't cause a memory leak?
class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
someKey: ''
};
}
someMethod = () => {
axios.get('some-url')
.then((response) => {
setState({someKey: response})
})
}
render(){
return(
<button onClick={this.someMethod}>Click Me</button>
)
}
}
export Schedule
If I leave the page before the new state is set if gives me this error:
'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.'
How do I handle this?
In general to handle API calls I would recommend you some app state management F.E Redux / MobX. But for simple usecase you can try go with:
class App extends Component {
constructor(props) {
super(props);
this.isMounted = false;
this.state = {
someKey: []
};
}
componentDidMount() {
this.isMounted = true;
}
someMethod = () => {
axios.get("some_url").then(res => {
if (this.isMounted) {
this.setState({
someKey: res
});
}
});
};
componentWillUnmount() {
this.isMounted = false;
}
render() {
...
}
}
Note that you can also cancel the axios request.
class App extends Component {
constructor(props) {
super(props);
this.source = axios.CancelToken.source();
this.state = {
someKey: []
};
}
someMethod = () => {
axios.get("some_url"{ cancelToken: this.source.token }).then(res => {
this.setState({
someKey: res
});
})
// catch axios cancel error, we don't want to show it
.catch(err => {
// if it's not an axios cancel error, we may want to re-throw the error (depending on the app structure)
if(!err.name === 'Cancel') {
throw err;
}
});
};
componentWillUnmount() {
this.source.cancel('Cancel message');
}
render() {
...
}
}
https://github.com/axios/axios#cancellation
I am new to the react and redux. Here is, what I am doing:
I have a component which is like ,
class LandingPage extends React.Component {
constructor(props) {
super(props);
this.state = {
isloading: true
}
}
componentDidMount() {
this.props.fetchJobDescription().then(() => {
this.setState({
isloading: false
})
});
}
render() {
if (this.state.isloading) {
return null;
}
else if (this.props.jobs && this.props.jobs.content && this.props.jobs.content.length > 0) {
return <JobList />;
}
else if (this.props.isError) {
return <ErrorComponent />
}
else {
return <Redirect to="/create-job" />
}
}
}
the action is like ,
export function fetchUserJd() {
return (dispatch) => {
let url = FETCH_JD_ROOT_URL + page + "&" + size;
dispatch({
type: REQUEST_INITIATED
})
return get(url)
.then((response) => {
if (response.status === 200) {
dispatch({
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: response.payload
})
dispatch({
type: REQUEST_SUCCESSED
})
} else {
if (!response.status) {
toastr.error('Our server is down. Please check again');
}
else if (response.status.status === 401) {
dispatch(logout());
}
else if (response.status.status === 500) {
toastr.error("Error while Fetching the job description,Please try again");
dispatch({
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: response.status,
});
dispatch({
type: REQUEST_SUCCESSED
})
} else {
dispatch({
type: REQUEST_SUCCESSED
})
}
}
})
return Promise.resolve();
}
};
Now,my logout is ,
export function logout() {
console.log("calling the logout action");
localStorage.clear();
history.push('/login');
return {
type: LOGOUT_REQUEST
}
}
class Header extends React.Component {
constructor(props) {
super(props);
}
logout = (e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
e.preventDefault();
this.props.logout();
}
render() {
return (
<Fragment>
<Navigation
isAuthenticated={localStorage.getItem("access_token") ? true : false}
operationType={this.props.operationType}
logout={this.logout} />
</Fragment>
)
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.LoginReducer.isAuthenticated,
operationType: state.Header.operationType,
}
}
Here, when there is a invalid token like while fetching it gives me 401 unauthorized, then I redirect use for the logout action. now,
when I do this that time , I get an 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 LandingPage (created by Context.Consumer)
in Connect(LandingPage) (created by Route)
How I can resolve this error ?
The issue is because you are setting state after component has unmounted. The issue might be that you make api hit, component unmounts, Then response of api is returned which sets the state. If you are using axios it can be handled.
// in the component
signal = axios.CancelToken.source();
// in componentWillUnmount
this.signal.cancel('API was cancelled');
Its a small issue that happens in your code. When you receive a 401 token, you try to redirect to logout from within the action creator using history.push which will unmount your LandingPage component, but at the same time you are trying to setState with loading: false, thus you receive this warning. The solution is simple
class LandingPage extends React.Component {
constructor(props) {
super(props);
this.state = {
isloading: true
}
this._mounted = true;
}
componentDidMount() {
this.props.fetchJobDescription().then(() => {
if (this_mounted) {
this.setState({
isloading: false
})
}
});
}
componentWillUnmount() {
this._mounted = false;
}
render() {
if (this.state.isloading) {
return null;
}
else if (this.props.jobs && this.props.jobs.content && this.props.jobs.content.length > 0) {
return <JobList />;
}
else if (this.props.isError) {
return <ErrorComponent />
}
else {
return <Redirect to="/create-job" />
}
}
}
or else in the action creator you can throw an error instead of dispatching the logout action and in the .catch block of fetchJobDescription dispatch the logout action
In LandingPage
this.props.fetchJobDescription().then(() => {
this.setState({
isloading: false
})
}).catch((err) => {
this.props.logout();
});
and in action creator
else if (response.status.status === 401) {
throw new Error('Error in status')
}