componentDidMount is being fired multiple times in ReactJs [duplicate] - reactjs

This question already has answers here:
ReactJs componentDidMount executes twice
(2 answers)
Closed 3 months ago.
I am calling an api using axios inside componentDidMount and the api is being called multiple times.
Here is my code
export default class Listings extends Component{
state = {
listings: null
}
//when the component is mounted to dom first
componentDidMount() {
this.getAllCryptoListings();
}
getAllCryptoListings() {
let response = null;
axios.get('http://localhost:8000/')
.then( (res) => {
response = res;
console.log(response.data.data);
})
.catch( (error) => console.log(error) )
.finally( () => { this.setState({listings: response.data.data}) } )
}
}
Normally I expected the code to run one time only as per the description of the function here componentDidMount.
The documentation says
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance.
How to make the code inside componentDidMount run only once?

componentDidMount() is called twice if you setState in it, you won't be able to disable this.
You can make your call in componentWillMount, but this is UNSAFE so be careful using it! (not recommended)
If this does not work you should check where your component is called. Maybe his parents are re-rendering and so calling the render to verify twice with the initial state.
You can read more about it here
In React version 18, a change was made to strict mode so that components will mount, then unmount, then mount again. This was added to help us all start catching issues that will affect an upcoming feature. In a future version of react, the state will be able to be preserved between unmounts, and as a result, components may mount multiple times.

Related

Trying to understand console.log behavior and state in react [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 7 months ago.
I'm trying to learn react and in doing so i need to do a lot of console.log(something). In the example below, could you please explain why the second console.log(this.state.foo);, the one within componentDidMount(), is not true? Thanks in advance.
Note: please don't suggest using functional components or hooks, the repo i will be working on is all class based components, so that's what i'm focusing on for now.
class App extends React.Component {
constructor (props){
super(props);
this.state = {
foo: false
}
console.log(this.state.foo);
}
componentDidMount() {
console.log('componentDidMount() lifecycle');
// Trigger update
this.setState({ foo: true });
// Why won't this one show in the console?
console.log(this.state.foo);
}
render() {
console.log('Render lifecycle')
return(
<h1>Hellos</h1>
)
}
}
ReactDOM.render( < App />, document.getElementById('container'));
That console.log is logging, but it's still logging false for the value this.state.foo. The reason for this is that setState is actually an asynchronous function (in other words, it takes time to execute completely), and you cannot expect the state update operation to have succeeded before you try to access the new value for state.
componentDidMount() {
console.log("componentDidMount() lifecycle");
// Updating state is asynchronous
this.setState({ foo: true });
// The line below is running, but the previous line updating state
// has not finished executing, which is why it's still logging false
console.log("component did mount", this.state.foo);
// => "component did mount", false
}
Here are the React docs on the topic.
As per docs: https://reactjs.org/docs/react-component.html#setstate
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
Your
// Why won't this one show in the console?
console.log(this.state.foo);
Works and shows initial false value, that is actually expected. It will be updated on next shouldComponentUpdate call.

setState updating but values not being retained [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I have a component fetching a API service that returns JSON. The service works, but the state on returns the json in the fetch after the second alert(this.state.questions); below.
My understanding of the execution stack of React is it shouldn't do that (I'm reasonable new to React). Or is it that state has a limited lifestyle time?
import React from 'react';
import { ConversationalForm } from 'conversational-form';
export default class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.submitCallback = this.submitCallback.bind(this);
}
componentDidMount() {
fetch('http://localhost:3000/api/questions')
.then((response) => response.json())
.then((responseJson) => {
this.setState({questions:JSON.stringify(responseJson)}, () => console.log(this.state.questions)); // Works fine, log has the json response
alert(this.state.questions); // Returns expected JSON - runs after the next alert
})
alert(this.state.questions); // returns undefined - runs first
this.cf = ConversationalForm.startTheConversation({
options: {
submitCallback: this.submitCallback,
showProgressBar: true,
preventAutoFocus: false,
},
tags: this.state.questions // returns undefined
});
this.elem.appendChild(this.cf.el);
}
submitCallback() {
var formDataSerialized = this.cf.getFormData(true);
console.log("Formdata, obj:", formDataSerialized);
this.cf.addRobotChatResponse("Your are done. Grab a well deserved coffee.")
}
render() {
return (
<div>
<div
ref={ref => this.elem = ref}
/>
</div>
);
}
}
i don't understand what's the problem, u expect the JSON to be at the second alert where there's undefined? which calls first ofc, because of the behavior of the async request, and it has nothing to do with react, it's a javascript thing.
if u need to run any code after there's a "questions" in the state, then u should use the componentDidUpdate and check if there's a "questions" in the state and then run the code.
if u would have used the React-Hooks it would be even easer :)
fetch(url) returns Promise.
In other words, it sends request to the url, it doesn't wait until receiving response and run next 'now' chunks - in this case, second alert command.
So if you are going to process response, you should implement the processing code in the second 'then'.
.then(responseJson) {
...
//process response
}
Or you can use async-await.
'Async-await' waits until coming response.
If you don't understand, please feel free to ask me.
You can't depend on a state value in the same function in which it was set. There is no guarantee that the state (which updates async) will have finished updating before you use it at the bottom of componentDidMount. You should either:
1: Use the raw value and not the state value.
2: Use the state value after you are certain that it exists.
Also Note: You are not waiting for the promise to resolve before trying to use the state.

Stop hooks re-fetching data on component update [duplicate]

This question already has answers here:
Infinite loop in useEffect
(16 answers)
Closed 3 years ago.
I am new to react hooks and as i read in docs useEffect runs on every update of component and on first render also (so it is componentDidMount and componentDidUpdate combined) but when i am fetching data inside useEffect it will re-do it when state updates and so if I set state value after fetching data from server it will create infinite loop. and i want to prevent it and only fetch it for first render like in componentDidMount, how can i implement this?
useEffect() takes a dependencies as an array parameter. This dependency(ies) acts as a decider such that, if one of the dependencies' value change, useEffect is invoked.
It's effectively equivalent to saying:
shouldComponentUpdate(nextProps, nextState) {
// If props or state changes, update the component
if(this.props.someProp != nextProps.someProp || this.state.someState != nextState.someState){
return true;
}
return false;
}
With useEffect() :
useEffect(() => {
//Do stuff here
}, [somePropValue, someStateValue]);
But to say that only do once, specify an empty dependency array(ideal for API calls that happens once on load):
useEffect(() => {
//Do stuff here
}, []);

Why useEffect doesn't fire on every render?

My component has two Rect.useEffect hook
const Example = ({ user }) => {
React.useEffect(() => {
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};
I update this component using mobx. It is re-rendered correctly. But "Why not me?" is printed only once.
As per official docs
By default, effects run after every completed render
This means console.log("Why not me?"); too should run every time prop user is updated. But it doesn't. The console output is this
What's the reason behind this apparent inconsistency?
My complete code can be viewed here
In Mobx, just like Observer component which provides a render function callback, autorun function also executes independently of the react lifecycle.
This behaviour happens because you have user count as a observable variable.
According to the mobx-react docs
Observer is a React component, which applies observer to an anonymous
region in your component. It takes as children a single, argumentless
function which should return exactly one React component. The
rendering in the function will be tracked and automatically
re-rendered when needed.
and mobx docs
When autorun is used, the provided function will always be triggered
once immediately and then again each time one of its dependencies
changes.
You can confirm this behvaior by logging directly inside the functional component and you will observer that the component is only rendered once
EDIT:
To answer your question
If I change useEffect to this
React.useEffect(autorun(() => {console.log("inside autorun", user.count)}));
basically remove anonymous function from useEffect and just pass
autorun directly, then it is run only once. Why is it so? What's the
difference?
The difference is that autorun returns a disposer function which when run will dispose of the autorun and would not longer execute it.
From the docs:
The return value from autorun is a disposer function, which can be
used to dispose of the autorun when you no longer need it.
Now what happens is that since useEffect executes the callback provided to it when it runs, the callback executed is the disposer function returned by autorun which essentially cancels your autorun.
It looks like your component doesn't rerender. autorun receives a callback and might call it independently from render.
Example component rerenders only when its parent rerenders or when its props change.
Use this code to observe what's really happening:
const Example = ({ user }) => {
console.log('render');
React.useEffect(() => {
console.log('useEffect autorun');
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};

Why in React componentWillReceiveprops fires before setState() in componentDidMount?

I have been programming with React for a while now but I have never faced this annoying issue, in one of my components componentWillReceiveProps fires before setState() in componentDidMount gets executed. This causes several issues in my application.
I have a variable this.props.flag received from props which is going to be stored in the state of the component:
componentDidMount() {
if (!_.isEmpty(this.props.flag)) {
console.log('Flag Did:', this.props.flag);
this.setState({
flag: this.props.flag
},
() => doSomething()
);
}
In my componentWillReceiveProps method the variable this.state.flag is going to be replaced just if it is empty or if it different from the value of this.props.flag (the checks are made by using the lodash library):
componentWillReceiveProps(nextProps) {
const { flag } = this.state;
console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);
if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
this.setState({
flag: nextProps.flag,
},
() => doSomething()
);
}
}
Suppose that the prop flag in this case has always the same value and that this.state.flag is initialized to undefined. When I check the console log I see the following result:
Flag Did: true
Flag Will: true undefined true
Therefore when the code enters componentWillReceiveProps the value of this.state.flagis still undefined, that means has not been set yet by the setState in componentDidMount.
This is not consistent with React lifecycle or am I missing something? How can I avoid such behaviour?
ComponentWillReceiveProps() will be called in each update life-cycle caused by changes to props (parent component re-rendering). Since Javascript is synchronous you might have validate props sometimes to save app crashes. I've not totally understood the context of your app but what you can do is:
componentWillReceiveProps(nextProps) {
const { flag } = this.state;
if(!flag){
return;,
}
console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);
if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
this.setState({
flag: nextProps.flag,
},
() => doSomething()
);
}
}
You can return if state is undefined. It will be called again upon parent re-rendering. But this might not be use-case.
Anyways you should look into this:
But I can think of at least 1 (maybe theoretical) scenario where the order will reversed:
Component receives props, and starts rendering. While component is
rendering, but has not yet finished rendering, component receives new
props. componentWillReceiveProps() is fired, (but componentDidMount
has not yet fired) After all children and component itself have
finished rendering, componentDidMount() will fire. So
componentDidMount() is not a good place to initialise
component-variables like your { foo: 'bar' }. componentWillMount()
would be a better lifecycle event. However, I would discourage any use
of component-wide variables inside react components, and stick to
design principles:
all component variables should live in either state or props (and be
immutable) all other variables are bound by the lifecycle method (and
not beyond that)
As suggested by user JJJ, given the asynchronous nature of setState, the check if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) in componentWillReceiveProps is executed before setState inside componentDidMount executes flag: this.props.flag. The order of the operations is:
Code enters componentDidMount.
Code executes setState in
componentDidMount (flag: this.props.flag hasn't happened yet).
Code exits componentDidMount, setState in componentDidMount is
still under execution (flag: this.props.flag hasn't happened yet).
Component receive new props, therefore enters
componentWillReceiveProps.
The statement if
(!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) in
componentWillReceiveProps is executed (this.state.flag is still
undefined).
Code finishes the execution of setState inside
componentDidMount and sets flag: this.props.flag and executes
doSomething().
Code finishes the execution of setState inside
componentWillMount and sets flag: nextProps.flag and executes
doSomething().
Given the asynchronous nature of setState, 6 and 7 could be executed in parallel and therefore we do not know which one will finish its execution first. DoSomething() in this case is potentially called at least 2 times when it must be called once instead.
In order to solve these issues, I changed my code this way:
componentWillReceiveProps(nextProps) {
if (!_.isEmpty(nextProps.flag) && !_.isEqual(this.props.flag, nextProps.flag)) {
this.setState({
flag: nextProps.flag,
},
() => doSomething()
);
}
}
This way I compare the new version(nextProps) with the old version(this.props) of the props, without waiting for the flag value to be stored in the component's state.

Resources