I am fetching data in componentDidMount and updating the state and the famous warning is appearing:
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.
state = {
post: null
}
render() {
console.log(this.props.postId)
if (this.props.post) {
return (
<div>
<h3> {this.state.post.postTitle} </h3>
<p> {this.state.post.postBody} </p>
</div>
);
} else {
return <Redirect to="/blog" />
}
}
componentDidMount() {
this._isMounted = true;
axios
.get('https://jsonplaceholder.typicode.com/posts/' + + this.props.postId)
.then((response) => {
let post = {
id: response.data.id,
postTitle: response.data.title,
postBody: response.data.body
}
this.setState((prevState,props)=>{
console.log('post',post)
console.log('prevState',prevState)
console.log('props',props)
})
})
.catch((e) => {
console.log('error', e)
})
}
}
What is causing this warning and what is the best way to fetch the data and update the state?
Your question is the same as the following question.
How the component is developed, you can enter a loop and execute the render infinitely. To prevent it, add one more state and control to make the request only 1 time or those that need but that are "mindful".
Also, your code needs some changes, you can use the State to validate whether to show something or not and once the request is made, set the state again. With some change your code can look like this:
state = {
loadingData: true,
post: null
}
render() {
if (!this.state.loadingData) {
return (
<div>
<h3> {this.state.post.postTitle} </h3>
<p> {this.state.post.postBody} </p>
</div>
);
} else {
return <Redirect to="/blog" />
}
}
componentDidMount() {
this._isMounted = true;
if(this.state.loadingData)
axios
.get('https://jsonplaceholder.typicode.com/posts/' + + this.props.postId)
.then((response) => {
this.setState({
loadingData: false,
post: {
id: response.data.id,
postTitle: response.data.title,
postBody: response.data.body
}
})
})
.catch((e) => {
console.log('error', e)
})
}
I hope it's useful.
Regards
Just create flag in state: isMounted: false. In componentDidMount set state isMounted = true and in componentWillUnmount: set state isMounted = false.
And in the async function that need use setState function, wrap them by condition if(this.state.isMounted) { ....setState action }
Related
I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}
I'm now to react and I'm wondering if what I've done is a bad way of creating this component. What I wonder is:
Is this the correct way to do the callback in the setState? If not, where should this line $('#editor').data('kendoEditor').value(data) be placed?
componentDidUpdate(prevProps) {
if(this.props.id!== prevProps.id) {
$.get('/webapi/GetData?id=' + this.props.id, function (data) {
this.setState({ editorValue: data }, $('#editor').data('kendoEditor').value(data));
}.bind(this));
}
}
Why doesn't this work?
componentDidMount() {
this.initEditor();
$.get('/webapi/GetData', function (data) {
this.setState({ data: data });
}.bind(this));
}
initEditor = () => {
$("#editor").kendoEditor({
value: this.state.editorValue,
)}
}
but this works?
componentDidMount() {
$.get('/webapi/GetData', function (data) {
this.setState({ data: data });
this.initEditor();
}.bind(this));
}
To properly do a callback after setState follow this format:
this.setState( { foo: bar }, () => callbackFunction() )
EDIT
To answer the second part of your question, you shouldn't need to use those lines of code at all. Let React handle the rendering. Say you have a render like so
render() {
return(
<div>
<input type="text" name="someValue" data-kendoeditor={this.state.editorValue} />
</div>
)
}
Then call setState like:
componentDidUpdate(prevProps) {
if(this.props.id!== prevProps.id) {
$.get('/webapi/GetData?id=' + this.props.id, function (data) {
this.setState({ editorValue: data });
}.bind(this));
}
}
This will rerender the value from state to the DOM.
The scenario is, after mounting the component, in event listener I am setting a state variable and other state variables are being set by making a rest call from backend.
so far what I did is I am using componentWillUpdate and making rest call and setting all the required states.
I tried using componentWillUpdate method to calculate and set other state variables. But its re-rendering multiple times. I guess I am definitely doing something wrong here.
export default class Person extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
name: this.props.name,
age: "",
country: ""
};
}
componentDidMount() {
this.setDerivedStates();
}
componentWillUpdate() {
this.setDerivedStates();
}
attachListner() {
document.addEventListner("customEvent", () => {
this.setState({ name: something });
});
}
setDerivedStates() {
FetchService.get("url1" + this.state.name).then(response =>
this.setState({ age: response.age})
);
FetchService.get("url2" + this.state.name).then(response =>
this.setState({ country: response.country })
);
}
render() {
return (
<div>
<p>{this.state.name}</p>
<p>{this.state.age}</p>
<p>{this.state.country}</p>
</div>
);
}
}
I want to re-render the component once with all the new state variables.
Please suggest how should I do it. which lifecycle method and how should I use to set all these states?
You can use Promise.all to batch the two fetches so you only have call this.setState once -
const [resp1, resp2] = await Promise.all([
FetchService.get("url1" + this.state.name);
FetchService.get("url2" + this.state.name);
]);
this.setState({ age: resp1.age, country: resp2.country });
Also, componentWillUpdate is considered unsafe and will be deprecated in the future. I would suggest using componentDidUpdate instead -
componentDidUpdate = (prevProps, prevState) => {
if (prevState.name !== this.state.name) {
this.setDerviedStates();
}
}
You can wrap both fetches in Promise.all which will wait for both Promises to resolve, if one fails you will have no access to any Promise/s that resolves successfully and the operation will throw an error.
Add componentDidUpdate to check if the name state has changed, if so refetch.
componentDidUpdate(prevProps, prevState) {
if (prevState.name !== this.state.name) {
this.setDerivedStates();
}
}
async setDerivedStates() {
const url = `url${this.state.name}`;
try {
const [age, country] = await Promise.all([
FetchService.getAge(url),
FetchService.getCountry(url),
]);
this.setState({ age, country });
} catch (e) {
console.log('something went wrong', e);
}
}
I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question
I have a table of ships, and am trying to implement sorting (using table header clicks) and filtering (using a text field that the user types in).
I am puzzled by how React handles the state of my component.
My understanding is that componentDidUpdate() works like this:
I make a change to the component state somewhere
The state change is detected by the component and componentDidUpdate() runs
Based on this understanding, I expected componentDidUpdate() to
Re-sort when I change the state of ships
Re-filter when I change the state of ships
However, when a sorting is triggered, filtering is not done.
I thought that this would happen:
State is changed, triggering componentDidUpdate()
Ships are sorted
The state is saved
The saving of the state triggers a re-run of componentDidUpdate()
this.state.ships is now different from prevState.ships, triggering a re-filtering
But this seems to happen:
State is changed, triggering componentDidUpdate()
Ships are sorted
The state is saved
The saving of the state triggers a re-run of componentDidUpdate()
this.state.ships is the same as prevState.ships, not triggering a re-filtering
So either my understanding of componentDidUpdate() is spotty, or my understanding of state synchronicity is. I have read that state can be asynchronous in event handlers. Perhaps the sorted ships are not yet saved into the state when I try to detect if I should be filtering?
import React, { Component } from 'react';
import { SearchBar } from '../SearchBar';
import { Table } from '../Table/Table';
import { MoreButton } from '../MoreButton/MoreButton';
export class SearchableSortableTable extends Component {
constructor(props) {
super(props);
this.fetchShips = this.fetchShips.bind(this);
this.filterShips = this.filterShips.bind(this);
this.setSearchExpression = this.setSearchExpression.bind(this);
this.setSort = this.setSort.bind(this);
this.state = {
ships: [],
filteredShips: [],
searchExpression: '',
reverseSort: false
};
}
render() {
return (
this.state.error ?
<div>
<div>There was a problem fetching the ships, sorry.</div>
<div>{this.state.error}</div>
</div>
:
this.state.ships.length === 0 ? <h4>Loading...</h4> :
<div>
<div>
<SearchBar setSearchExpression={this.setSearchExpression} />
<MoreButton className="di" url={this.state.nextUrl} fetchShips={this.fetchShips} />
</div>
<div>
<Table ships={this.state.filteredShips} setSort={this.setSort} sortBy={this.state.columnName} reverse={this.state.reverseSort} />
</div>
</div>
);
}
componentDidMount() {
this.fetchShips(this.props.url);
}
componentDidUpdate(prevProps, prevState) {
if (this.state.columnName !== prevState.columnName || this.state.reverseSort !== prevState.reverseSort) {
this.sortShips();
}
// This conditional block is not entered when I sort.
if (this.state.ships !== prevState.ships || this.state.searchExpression !== prevState.searchExpression) {
this.filterShips();
}
}
async fetchShips(url) {
try {
const response = await fetch(url);
if (response['status'] && response['status'] === 200) {
const json = await response.json();
const ships = json['results'].map(this.mapShip);
this.setState({
ships: this.state.ships.concat(ships),
nextUrl: json['next']
});
} else {
this.setState({ error: `${response['status']} ${response['statusText']}` });
}
} catch (error) {
if (error instanceof TypeError && error.message.includes('NetworkError')) {
this.setState({ error: `${error.name} ${error.message}` });
} else {
throw error;
}
}
}
filterShips() {
const filteredShips = this.state.ships.filter(ship => {
return Object.values(ship).some(shipProp => shipProp.includes(this.state['searchExpression']))
});
this.setState({
filteredShips: filteredShips
});
}
setSearchExpression(event) {
this.setState({ searchExpression: event.target.value });
}
setSort(event) {
if (event && event['currentTarget'] && event['currentTarget']['attributes'] &&
event['currentTarget']['attributes']['name'] && event['currentTarget']['attributes']['name']['nodeValue']) {
const columnName = event['currentTarget']['attributes']['name']['nodeValue'];
this.setState({
columnName,
reverseSort: columnName === this.state.columnName ? !this.state.reverseSort : false
});
}
}
sortShips() {
if (this.state.columnName) {
const sortedShips = this.state.ships.sort((a, b) => {
const propA = a[this.state.columnName];
const propB = b[this.state.columnName];
if (!isNaN(+propA)) {
return this.state.reverseSort ? Number(propB) - Number(propA) : Number(propA) - Number(propB);
}
return this.state.reverseSort ? propB.localeCompare(propA) : propA.localeCompare(propB);
});
this.setState({ ships: sortedShips });
}
}
/**
* Maps a ship to its name, manufacturer, cost and starship class.
* #param ship The ship to be mapped.
*/
mapShip(ship) {
const { name, manufacturer, cost_in_credits, starship_class } = ship;
return Object.assign(
{
name,
manufacturer,
cost_in_credits,
starship_class
},
{}
);
}
}
The shouldComponentUpdate() method works for both props and state. In your example, after the sort/filter events, the following method is fired by React. Try using,
shouldComponentUpdate(nextProps, nextState) {
return this.state.value != nextState.value;
}