Set state in componentDidMount outside of a callback - javascript

I've read that I should not setState directly in componentDidMount, but setting it in a callback is fine (that's why it's the place for ajax calls).
The problem is that I set the state after parsing the search params like this
componentDidMount() {
let price = 0;
const query = new URLSearchParams(this.props.location.search);
for (let param of query.entries()) {
if (param[0] === 'price') {
price = param[1];
}
}
this.setState({ price: price });
}
I then do a conditional rendering: if the price isn't set, I render nothing.
I understand that this will cause 2 renders: The first one will be blank, and the second one will be the intended one, after the price is set.
So, is there any problem with calling setState directly inside componentDidMount? Is there a better alternative?
I don't want to use componentWillMount due to its recent deprecation (react 16.3).

constructor(props){
super(props);
this.state = {
price:false
};
}
Now create a function
togglediv{(this.setState({price:!this.state.price})}
and you can use conditional rendering on the div which you want to show /hide

Related

Javascript object vs array ask Axios.get [duplicate]

Is there a reason that calling setSate() in a loop would prevent it from updating the state multiple times?
I have a very basic jsbin that highlights the problem I am seeing. There are two buttons. One updates the state's counter by 1. The other calls the underlying function of One in a loop -- which seemingly would update the state multiple times.
I know of several solutions to this problem but I want to make sure that I am understanding the underlying mechanism here first. Why can't setState be called in a loop? Do I have it coded awkwardly that is preventing the desired effect?
From the React Docs:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
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.
Basically, don't call setState in a loop. What's happening here is exactly what the docs are referring to: this.state is returning the previous value, as the pending state update has not been applied yet.
There's a nice way to update state in a loop. Just make an empty variable, set its value to the updated state, call setState(), and pass it this variable:
const updatedState = {};
if (vars.length) {
vars.forEach(v => {
updatedState[v] = '';
this.setState({
...this.state
...updatedState,
});
});
}
You have to use something like that:
const MyComponent = () => {
const [myState, setMyState] = useState([]);
const handleSomething = (values) => {
values.map((value) => {
setMyState((oldValue) => [...oldValue, { key: value.dataWhatYouWant }]);
}
}
return (<> Content... </>);
}
I had the same problem. But tried with a little different approach.
iterateData(data){
//data to render
let copy=[];
for(let i=0;<data.length;i++){
copy.push(<SomeComp data=[i] />)
}
this.setState({
setComp:copy
});
}
render(){
return(
<div>
{this.state.setComp}
</div>
);
}
I hope this helps.
Basically setState is called asynchronously. It also has a callback function which you can utilise to do something once the state has been mutated.
Also if multiple setStates are called one after the other they are batched together as written previously.
Actually setState() method is asynchronous. Instead you can achieve it like this
manyClicks() {
var i = 0;
for (i = 0; i < 100; i++) {
//this.setState({clicks: this.state.clicks + 1}); instead of this
this.setState((prevState,props)=>({
clicks: ++prevState.clicks
}))
}
}
I was having this issue when creating a feature to import items.
Since the amount of the importing items could be huge, I need to provide feedback (like a progress bar) to the site user so that they know that they aren't sitting there and waiting for nothing.
As we know that we can't setState in a loop, I took a different approach by running the task recursively.
Here's a example code
https://codesandbox.io/s/react-playground-forked-5rssb
You can try this one using the previous value to increase the count.
function handleChange() {
for (let i = 0; i < 5; i++) {
setState(prev => {
return prev + 1
})
}
}
I was able to make your code work, calling setState in the loop by doing the following:
manyClicks() {
for (i = 0; i < 100; i++) {
this.setState({clicks: this.state.clicks += 1})
}
}
enter code here
Hopefully this helps!

React componentDidMount confusion

I'm confused by some behavior and wondering if someone can help. I have a React component which fetches movie data depending on the filter passed in through props. Using console.log I can see that my componentDidMount() is only being called once, yet each time the component is re-rendered due to receiving different props a state variable that is only set in componentDidMount() changes. My code is quite long so I don't want to post it all but I can if needed. The snippet that is causing my confusion is below:
class MoviesList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterList : [], filter: {}
};
}
async componentDidMount() {
console.log("shouldOnlyCallOnce");
if( this.props.filter.on) {
if (this.props.filter.type == "title"){
try {
const url = "mycustomapi.com/api/"+this.props.filter.title;
const response = await fetch(url);
const jsonData = await response.json();
jsonData.sort((a, b) => a.title.localeCompare(b.title));
console.log("??");
this.setState({filterList: jsonData, filter: this.props.filter});
}
catch (error) {
console.error(error);
}
}
Despite the state.filter only being set there in the whole component, it changes each time my component is reloaded. Does anyone know how this could happen without console logging multiple times?
As I see it, the only explanation is that you are modifying the contents of the filter outside this component.
Consider this:
let filterA = { title: 'foo' }
let filterB = filterA
console.log(filterB.title) // Shows 'foo'
filterA.title = 'bar'
console.log(filterB.title) // Shows 'bar'
Even though we never directly modified filterB here, the value changed. This is because filterA and filterB are referencing the same object, so of course they both change.
If you in your case pass the filter as a prop, and later modify the filter inside the parent, the filter will have changed in the child as well.
As a side note: What you are doing is called deriving state from props, which means that you are using the props to set the state. This is almost never good practice. https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
ComponentDidMount will trigger when a component will do intiate render. After a props or state change, ComponentDidUpdate will trigger .Please check the link : react life cycle example

Why I am getting the old state values after the props change

I just want to understand why in the application I have following situation, below is my constructor of class component:
constructor(props) {
super(props);
this.state = {
tableAlerts: props.householdAlerts,
initialAlerts: props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}
in render function I have:
const { householdAlerts } = this.props;
My issue is that in constructor I got empty array, but in render funtion I have the data. Is it possible to get the data in constructor?
This is a very bad pattern when using the class component. You are ignoring any props updates when you copy the value into state. to manage it:
It requires you to manage two sources of data for the same variable: state and props. Thus, you need to add another render each time your prop change by setting it into state (don't forget to test on equality from prev and next values to avoid being in an infinite loop).
You can avoid setting the state each time your props change by using the getderivedstatefromprops lifecycle method.
So the recommendation is: just use the props; do not copy props into state.
To learn more why you shouldn't, I highly recommend this article.
It is not recommended to set your initial component state in the constructor like so because you gonna lose the ability to use { setState } method after to update this property/state.
The best practice is indeed to refer directly to the prop with { this.prop.householdAlerts }, and keep the state usage for local (or in child components} cases.
if anyhow you want to store props in component state for some reason, call it in lifeCycle -
componentDidMount() {
const { tableAlerts, initialAlerts } = this.props;
this.setState({ tableAlerts, initialAlerts });
}
Hagai Harari is right. Nevertheless, your actual problem seems to be that during your initial rendering the array is empty. Can you ensure that the array has some items, when your component is rendered for the first time?
First rendering -> calls constructor
<YourComponent householdAlerts={[]} />
Second rendering -> updates component
<YourComponent householdAlerts={[alert1, alert2, alert3]} />
If you want initial state to have the prop value.Try something like this with 'this' keyword
constructor(props) {
super(props);
this.state = {
tableAlerts: this.props.householdAlerts,
initialAlerts: this.props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}

Real world usage of setState with an updater callback instead of passing an object in React JS

The React documentation says the following about setState:
If you need to set the state based on the previous state, read about the updater argument below,
Beside the following sentence, which I don't understand:
If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
They say:
The first argument is an updater function with the signature (state, props) => stateChange ... state is a reference to the component state at the time the change is being applied.
And make an example:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Saying:
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.
What do they mean by guaranteed to be up-to-date and what should we be aware of when deciding if we should use setState with an updater function (state, props) => stateChange or directly with an object as the first parameter?
Let's assume a real world scenario. Suppose we have a fancy chat application where:
The state of the chat is represented by this.state = { messages: [] };
Previous messages are loaded making an AJAX request and are prepended to the messages currently in the state;
If other users (not the current user) send a message to the current user, new messages arrive to current user from a realtime WebSocket connection and are appended to the messages currently in the state;
If it is the current user the one who sends the message, the message is appended to the messages of the state as in point 3 as soon as the AJAX request fired when the message is sent completes;
Let's pretend this is our FancyChat component:
import React from 'react'
export default class FancyChat extends React.Component {
constructor(props) {
super(props)
this.state = {
messages: []
}
this.API_URL = 'http://...'
this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
this.handleNewMessageFromOtherUser = this.handleNewMessageFromOtherUser.bind(this)
this.handleNewMessageFromCurrentUser = this.handleNewMessageFromCurrentUser.bind(this)
}
componentDidMount() {
// Assume this is a valid WebSocket connection which lets you add hooks:
this.webSocket = new FancyChatWebSocketConnection()
this.webSocket.addHook('newMessageFromOtherUsers', this.handleNewMessageFromOtherUser)
}
handleLoadPreviousChatMessages() {
// Assume `AJAX` lets you do AJAX requests to a server.
AJAX(this.API_URL, {
action: 'loadPreviousChatMessages',
// Load a previous chunk of messages below the oldest message
// which the client currently has or (`null`, initially) load the last chunk of messages.
below_id: (this.state.messages && this.state.messages[0].id) || null
}).then(json => {
// Need to prepend messages to messages here.
const messages = json.messages
// Should we directly use an updater object:
this.setState({
messages: messages.concat(this.state.messages)
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: messages.concat(state.messages)
.sort(this.sortByTimestampComparator)
}
})
// What if while the user is loading the previous messages, it also receives a new message
// from the WebSocket channel?
})
}
handleNewMessageFromOtherUser(data) {
// `message` comes from other user thanks to the WebSocket connection.
const { message } = data
// Need to append message to messages here.
// Should we directly use an updater object:
this.setState({
messages: this.state.messages.concat([message])
// Assume `sentTimestamp` is a centralized Unix timestamp computed on the server.
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: state.messages.concat([message])
.sort(this.sortByTimestampComparator)
}
})
}
handleNewMessageFromCurrentUser(messageToSend) {
AJAX(this.API_URL, {
action: 'newMessageFromCurrentUser',
message: messageToSend
}).then(json => {
// Need to append message to messages here (message has the server timestamp).
const message = json.message
// Should we directly use an updater object:
this.setState({
messages: this.state.messages.concat([message])
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: state.messages.concat([message])
.sort(this.sortByTimestampComparator)
}
})
// What if while the current user is sending a message it also receives a new one from other users?
})
}
sortByTimestampComparator(messageA, messageB) {
return messageA.sentTimestamp - messageB.sentTimestamp
}
render() {
const {
messages
} = this.state
// Here, `messages` are somehow rendered together with an input field for the current user,
// as well as the above event handlers are passed further down to the respective components.
return (
<div>
{/* ... */}
</div>
)
}
}
With so many asynchronous operations, how can I be really sure that this.state.messages will always be consistent with the data on the server and how would I use setState for each case? What considerations I should make? Should I always use the updater function of setState (why?) or is safe to directly pass an object as the updater parameter (why?)?
Thank you for the attention!
setState is only concerned with component state consistency, not server/client consistency. So setState makes no guarantees that the component state is consistent with anything else.
The reason an updater function is provided, is because state updates are sometimes delayed, and don't occur immediately when setState is called. Therefore, without the updater function, you have essentially a race condition. For example:
your component begins with state = {counter: 0}
you have a button that updates the counter when clicked in the following way: this.setState({counter: this.state.counter +1})
the user clicks the button really fast, so that the state does not have time to be updated between clicks.
that means that the counter will only increase by one, instead of the expected 2 - assuming that counter was originally 0, both times the button is clicked, the call ends up being this.setState({counter: 0+1}), setting the state to 1 both times.
An updater function fixes this, because the updates are applied in order:
your component begins with state = {counter: 0}
you have a button that updates the counter when clicked in the following way: this.setState((currentState, props) => ({counter: currentState.counter + 1}))
the user clicks the button really fast, so that the state does not have time to be updated between clicks.
unlike the other way, currentState.counter + 1 does not get evaluated immediately
the first updater function is called with the initial state {counter: 0}, and sets the state to {counter: 0+1}
the second updater function is called with the state {counter: 1}, and sets the state to {counter: 1+1}
Generally speaking, the updater function is the less error-prone way to change the state, and there is rarely a reason to not use it (although if you are setting a static value, you don't strictly need it).
What you care about, however, is that updates to the state don't cause improper data (duplicates and the like). In that case, I would take care that the updates are designed so that they are idempotent and work no matter the current state of the data. For instance, instead of using an array to keep the collection of messages, use a map instead, and store each message by key or hash that is unique to that message, no matter where it came from (a millisecond timestamp may be unique enough). Then, when you get the same data from two locations, it will not cause duplicates.
I'm not an expert in React by any means and have only been doing it for two months only, but here's what I learned from my very first project in React, which was as simple as showing a random quote.
If you need to use the updated state right after you use setState, always use the updater function. Let me give you an example.
//
handleClick = () => {
//get a random color
const newColor = this.selectRandomColor();
//Set the state to this new color
this.setState({color:newColor});
//Change the background or some elements color to this new Color
this.changeBackgroundColor();
}
I did this and what happened was that the color that was set to the body was always the previous color and not the current color in the state, because as you know, the setState is batched. It happens when React thinks it's best to execute it. It's not executed immediately. So to solve this problem, all I have to do was pass this.changeColor as a second argument to setState. Because that ensured that the color I applied was kept up to date with the current state.
So to answer your question, in your case, since you're job is to display the message to the user as soon as a new message arrives, i.e use the UPDATED STATE, always use the updater function and not the object.

Updating view from setState() not triggering rerender when expected

Answer:
I'm a bonehead. This is way late but don't want to leave a thread unanswered and especially since my initial answer was wrong. I needed to reference the new props I was getting, not this.props. As usual the answer was in the documentation. I updated my own answer below to reflect this.
Edit 1:
My first fiddle did not fully show my issue so I've updated it to demonstrate better. For some reason when I call my setState() I believe the first pass through it is undefined even though its given a value, yet on subsequent passes it works as expected. It seems like my initial setState() call is not triggering a rerender but all others are.
A bit different to the usual "setState not updating view" question as setState() IS updating my view and with the correct value. Just not when I expect it to. Basically I am triggering a setState() event should rerender my child component with new props which I believe should trigger the childs components componentWillReceiveProps lifecyle event, which will then call setState() within the child component and update its view. The issue is while it does update the view, it seems to do it a cycle behind when expected. In my MVP I call setState() at 2 seconds yet it updates the view at 4 seconds. I haven't been able to determine which part is the bad logic though.
Here is my jsFiddle MVP. Thanks for any suggestions.
Code:
class TaskBody extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillReceiveProps() {
this.setState({
activeTask: this.props.activeTask
});
}
render() {
return <div>
< h4 > {
this.state.activeTask ? this.state.activeTask : 'Task Title'
} < /h4>
< /div > ;
}
}
class Body extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTask: '',
counter: 1
};
this.updateActive = this.updateActive.bind(this);
}
updateActive(task) {
this.setState({
activeTask: task
});
}
componentDidMount(){
var self = this;
setInterval(function(){
if(self.state.counter === 4){
clearInterval(self.clickInterval);
return clearInterval(self.countInterval);
}
self.setState({ counter: self.state.counter + 1 });
}, 1000);
// imagine this was the click event, it should rerender the view
// instantaneously at 2 seconds because I called setState() right?
// which is supposed to trigger a re-render and then TaskBody should
// receive new props triggering it to setState() on its activeTask,
// which should update the view?
self.clickInterval = setInterval(function(){
self.setState({ activeTask: 'took double the time it should' });
}, 2000);
}
render() {
return <div>
< TaskBody activeTask = {
this.state.activeTask
}/>
<div>{this.state.counter}</div>
</div>;
}
}
ReactDOM.render(<Body />, document.querySelector('#body'));
The
self.setState({ activeTask: 'took double the time it should' });
is never actually called.
Here's why:
Your logic is located within the componentDidMount lifecycle method. If you read the documentation of componentDidMount, you'll see that it clearly states:
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs.
So, when componentDidMount gets called in your app, you're first checking the counter and calling a setState there. That alone will trigger a new render. This means that your second code block within componentDidMount is going to have no effect because while you set the method, it's never going to get called anywhere.
In my old answer, I was using a setTimeout() as a hack to get the results I wanted. Basically, if I wrapped my setState in a timeout it would set the state with the new props, but if I did not it would still reference the old props. Fortunately, this is already handled in react as componentWillReceiveProps already receives a newProps argument by default.
This:
componentWillReceiveProps(){
var self = this;
setTimeout(function(){
self.setState({activeTask: self.props.activeTask});
},0);
}
becomes
componentWillReceiveProps(newProps){
var self = this;
self.setState({activeTask: newProps.activeTask});
}

Categories

Resources