React componentDidUpdate makes infinit API calls on state change - javascript

I'm working with Class Components for the first time, and I'm facing an infinite loop.
My init state is
this.state = {
selectedYear: null,
error: null,
isLoaded: false,
items: [],
};
}
I have a function for fetching data
fetchData = () =>
fetch(
`https://jsonmock.hackerrank.com/api/football_competitions?year=${this.state.selectedYear}`
)
.then((res) => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.data,
});
},
(error) => {
this.setState({
isLoaded: false,
error,
});
}
);
and my componentDidUpdate method
componentDidUpdate(prevState) {
if (prevState.selectedYear != this.state.selectedYear) {
this.fetchData();
console.log(this.state.items);
}
}
The problem is when I change my year in state, fetchData gets called an infinite number of times.
It's beginner question but I never worked with class components.
Thanks

As per the React documentation, the signature of componentDidUpdate method is
componentDidUpdate(prevProps, prevState, snapshot)
what you are doing in your code is checking this.state.selectedYear with prevProps.selectedYear (this will be most probably undefined)
Try below,
componentDidUpdate(_, prevState) {
if (prevState.selectedYear != this.state.selectedYear) {
this.fetchData();
console.log(this.state.items);
}
}

Related

How to correctly use componentDidMount and componentDidUpdate in React.js?

I have a problem. I want to search for an index based on a url. Everything is sent to the components as it should, but there is an error after loading:
Cannot read property 'indexOf' of undefined
Data sent from JSON is sure to be transmitted and received correctly and is correctly assigned. The problem is most likely caused by badly applied 'componentDidMount' and 'componentDidUpdate'. How should it look correctly?
The data sent based on the URL of the page is 'this.props.brand'
Code:
class CarPage extends Component {
state = {
isLoading: true,
carData: [],
id: null
}
findMyIndex = () => {
this.setState({
id: this.carData.indexOf(this.props.brand),
})
}
componentDidUpdate() {
this.findMyIndex()
}
componentDidMount() {
fetch("/data.json")
.then(response => response.json())
.then(data => {
this.setState({
carData: data,
isLoading: false,
})
})
}
render() {
return (
<>
{!this.state.isLoading && (
<p>{this.state.carData[this.state.id].model}</p>
)}
</>
);
}
}
export default CarPage;
You don't need componentDidUpdate lifecycle method at all. You can do it like this:
class CarPage extends Component {
state = {
isLoading: true,
carData: [],
id: null
}
findMyIndex = () => {
return this.state.carData.map(el => el.brand).indexOf(this.props.brand);
}
componentDidMount() {
fetch("/data.json")
.then(response => response.json())
.then(data => {
this.setState({
carData: data,
isLoading: false,
})
})
}
render() {
return (
<>
{!this.state.isLoading && (
<p>{this.state.carData[this.findMyIndex(this.props.brand)].model}</p>
)}
</>
);
}
}
export default CarPage;
It seems that findMyIndex returns -1 and this.state.carData[this.state.id] is equal to undefined. Check if CarData indeed has a this.props.brand entry.

Console.log() shows undefined before getting data

I seem to have a lifecycle hook issue that I can't seem to solve.
export default class EditRevision extends Component {
state = {
data: [],
customColumns: []
}
componentWillMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
}
render() {
/* THIS IS THE CONSOLE.LOG() I AM REFERRING TO */
console.log(this.state.data.subscriptionRevisionDTOS)
return (
<div></div>
)
}
}
And this is my log upon rendering the component
https://i.gyazo.com/9dcf4d13b96cdd2c3527e36224df0004.png
It is undefined, then retrieves the data as i desire it to, then it gets undefined again.
Any suggestions on what causes this issue is much appreciated, thank you.
Replace this:
componentWillMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
with:
constructor(props){
super(props)
this.state = {
data: [],
customColumns: []
}
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
}
try to call axios in constructor or componentDidMount() (componentWillMount should not be used). the undefined result is caused by the async call. Looks like you have a lot of uncontrolled renders. try to add a shouldComponentUpdate function or convert your component in a PureComponent
Take a look at https://reactjs.org/docs/react-component.html
You have init the state with
state = {
data: [],
customColumns: []
}
Here this.state.data is empty array which did not have definition of
subscriptionRevisionDTOS that is why you are getting this.state.data.subscriptionRevisionDTOS undefined.
Meanwhile, your asyncaxios.get call is completed and this.state.data is updated with subscriptionRevisionDTOS.
As soon as state is updated render() called again and you are getting the proper value of this.state.data.subscriptionRevisionDTOS.
So below line will surely work.
state = {
data:{subscriptionRevisionDTOS:[]},
customColumns: []
}
export default class EditRevision extends Component {
state = {
data:{subscriptionRevisionDTOS:[]},
customColumns: []
}
componentDidMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' +
(this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
render() {
/* THIS IS THE CONSOLE.LOG() I AM REFERRING TO */
console.log(this.state.data.subscriptionRevisionDTOS)
return (
<div></div>
)
}
see this it should be like this

React setState() after fetch not rerendering

I am fetching data in componentDidMount() (I am getting them in the form I want) and I want to save them in the component state with this.setState.
The state is not changing.
I console log that I am getting to the point where setState is called - there are conditions
I tried const that = this
The component is not re-rendering and state is not changing and I would like to know why.
My code:
export class Offers extends Component {
constructor(props) {
super(props);
this.renderOffer = this.renderOffer.bind(this);
this.state = {
...
};
}
componentWillMount() {
this.setState(() => ({
offer: {},
isLoading: true,
isMyOffer: false,
...
}));
}
componentDidMount() {
console.log('MOUNTED');
const { profile } = this.props;
if (profile) {
this.setState(() => ({
isLoading: false
}));
}
if (profile && profile._id) {
this.setState(() => ({
isMyOffer: true,
...
}));
fetch(`/api/offers-by/${profile._id}`,{
method: 'GET'
})
.then(response => response.json())
.then(offers => {
if(!offers || !offers.length) {
this.setState(() => ({
isLoading: false
})
);
} else {
console.log('ELSE', offers[0]._id); // getting proper data
console.log('THIS', this) // getting this object
const offerData = offers[0]
this.setState(() => ({
offer: offerData,
isLoading: false
})) // then
}}) // fetch
console.log('STATE', this.state)
}
console.log('STATE', this.state)
}
setState has a callback method as the second argument.You should use that after the initial setState.This works because setState itself is an asynchronous operation.The setState() method does not immediately update the state of the component but rather if there are multiple setStates, they will be batched together into one setState call.
this.setState(() => ({
isLoading: false
}),() =>{
/// You can call setState again here and again use callback and call fetch and invoke setState again..
});
Ideally you could refactor some of your setStates into a single setState call.Start with an empty object and add properties to your object based on conditons.
const updatedState ={}
if(loading){
updatedState.loading = false
}
if(profile &&..){
updatedState.someProperty = value.
}
this.setState(updatedObject,()=> {//code for fetch..
}) // Using the object form since you don't seem to be in need of previous State.

React setState twice renders state

When I trade with setState, it turns twice. First the Data Fail is returned and then the Data Ready is returned. Console return false first then true. I just want to return true in the console.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: []
};
}
componentDidMount() {
fetch("https://reqres.in/api/users?page=2")
.then(res => res.json())
.then(result => {
this.setState({
data: result.data,
loading: true
});
});
}
render() {
const { loading } = this.state;
console.log(loading);
const dataReady = <div>Data Ready </div>;
const DataFail = <div>Data Fail </div>;
return <div>{loading === true ? dataReady : DataFail}</div>;
}
}
Example:
https://codesandbox.io/s/kopz8wrl7o
You simply reversed the boolean associated with the loading. You should initially set it to true and then to false when your data is loaded.
Working example :
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
error: false,
data: []
};
}
componentDidMount() {
fetch("https://reqres.in/api/users?page=2")
.then(res => res.json())
.then(result => {
this.setState({
data: result.data,
loading: false
});
})
.catch(error =>{
this.setState({ error: true });
});
}
render() {
const { loading, error } = this.state;
!loading && console.log(true)
return <div>{!loading && <div>Data Ready </div>}
{error && <div>Data Error :( </div>}
</div>;
}
}
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.1.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.1.1/umd/react-dom.production.min.js"></script>
<div id='root'/>
your data changes that's why you have two messages:
you need did decision, what's you gonna do?
if you didn't want change state you can use shouldComponentUpdate, or you can just skip 1 console message
It is due to the React's lifecycle and the fact that you are setting the state.loading property initially to false. https://reactjs.org/docs/react-component.html
First there is the initial render, then the componentDidMount with its setState is called. You might want to leave the loading undefined and then in the render method check if its defined or not before checking its value.

Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application

Why am I getting this error?
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.
postAction.js
export const getPosts = () => db.ref('posts').once('value');
components:
constructor(props) {
super(props);
this.state = { posts: null };
}
componentDidMount() {
getPosts()
.then(snapshot => {
const result = snapshot.val();
this.setState(() => ({ posts: result }));
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
this.setState({ posts: null });
}
render() {
return (
<div>
<PostList posts={this.state.posts} />
</div>
);
}
As others mentioned, the setState in componentWillUnmount is unnecessary, but it should not be causing the error you're seeing. Instead, the likely culprit for that is this code:
componentDidMount() {
getPosts()
.then(snapshot => {
const result = snapshot.val();
this.setState(() => ({ posts: result }));
})
.catch(error => {
console.error(error);
});
}
since getPosts() is asynchronous, it's possible that before it can resolve, the component has unmounted. You're not checking for this, and so the .then can end up running after the component has unmounted.
To handle that, you can set a flag in willUnmount, and check for that flag in the .then:
componentDidMount() {
getPosts()
.then(snapshot => {
if (this.isUnmounted) {
return;
}
const result = snapshot.val();
this.setState(() => ({ posts: result }));
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
this.isUnmounted = true;
}
React component's state is a local entity. Unmounted component dont have state, no need to do that. React is already telling you this is a no-op which means no-operation in tech speak. It means that you telling component to do something when its already destroyed.
https://reactjs.org/docs/react-component.html#componentwillunmount
You should not call setState() in componentWillUnmount() because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again.
Remove this
componentWillUnmount() {
this.setState({ posts: null });
}
it's useless
From the doc:
You should not call setState() in componentWillUnmount() because the
component will never be re-rendered. Once a component instance is
unmounted, it will never be mounted again.
https://reactjs.org/docs/react-component.html#componentwillunmount
You can try this code:
constructor(props) {
super(props);
this.state = { posts: null };
}
_isMounted = false;
componentDidMount() {
this._isMounted = true;
getPosts()
.then(snapshot => {
const result = snapshot.val();
if(this._isMounted) {
this.setState(() => ({ posts: result }))
}
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
this._isMounted = false;
this.setState({ posts: null });
}
render() {
return (
<div>
<PostList posts={this.state.posts} />
</div>
);
}
By using _isMounted, setState is called only if the component is mounted. the answer simply does a check to see if the component is mounted before setting the state.

Categories

Resources