React - How can I make one time only rendering value in state - reactjs

What I need to do is to setState with a value, then send data to a children by props, but I would like "state" to forget about that after doing this once.
this.setState({
animateId: 15 //or any number of id
});
then later
if(this.state.animateId){
let idToSent = this.state.animateId;
}
<Items <...different props> animate={idToSent}/> //once after settingState - 15,
// then undefined or whatever with every next time
and I would want to take this value once, send it via props, and forget about it, either way.
Is there any way to do that, beside just altering that one value in state again, because that would cause unnecesary rendering ??

We can set it to undefined like this after our task is complete
this.setState({ doSomething: undefined });

I would let the Item component keep track of when and when not to initiate the animation based on comparing the latest props with the local state.
I don't know exactly how you are triggering the animation, but it sound like you could do something like this in the Item component
const { animateId } = this.props;
if (animateId !== this.state.animateId) {
// Whatever code that starts your animation...
// Then either update the state right away to signal the last animation
// that was triggered, or if it can't be done here, then set it in a
// callback from when the animation is done.
this.setState({ animateId });
}
Just keep in mind that if this code is executed during the render phase, then it probably won't let you call setState.

Related

React sometimes rerenders between two setStates, depending on setState order

I've got two simple useStates.
At one point, they're set, one after another. This happens within a useEffect with [] deps.
canvas.toBlob(blob => {
if (blob) {
setBounds([width, height]);
setUrl(URL.createObjectURL(blob));
} else console.log("no blob");
});
The above code (first set bounds, then url) causes the component to rerender once, with both states set properly.
However, the code below (first url, then bounds):
canvas.toBlob(blob => {
if (blob) {
setUrl(URL.createObjectURL(blob));
setBounds([width, height]);
} else console.log("no blob");
});
makes the component rerender twice. The first render has only the url set, then only on the second render, the bounds are set as well.
Why does changing the order of these two lines change the rerenders?
I think the reason is because you're inside a callback function, React isn't batching updates.
So the order of these states being set actually matters and somehow setting the url before the bounds is triggering an additional render which I cannot make out from the given code.

Wait for Redux props and then do some operations on the props in componentDidMount

I have a ChatBox component for my website, which displays the chat history of a user. The chat history is not ordered in my firestore, so I want to (1) sort it from the most to the least recent in componentDidMount once I get the data via Redux props (this.props.profile.chats_history) and (2) set the field "chatlist" in the state of ChatBox to the sorted array. The problem is that it takes time for props to be received and when the array.sort() method was called the console reported that the array was undefined. I tried to get around it by using async and await keywords but my solution did not work.
My solution
async componentDidMount() {
let chatlist;
await this.props.profile.chats_history;
chatlist = this.props.profile.chats_history.sort(function(a, b) {return a.time - b.time});
this.setState({
chatlist: chatlist
})
}
What you can do is wait for chats_history to be updated, using componentDidUpdate instead of componentDidMount. Here I'm doing a deep equal on this.props.chats_history.
const _ = require("lodash")
componentDidUpdate(prevProps, prevState) {
if (!_.isEqual(prevProps.profile.chats_history, this.props.profile.chats_history)) {
chatlist = this.props.profile.chats_history.sort(function(a, b) {return a.time - b.time});
this.setState({
chatlist: chatlist
})
}
}
Basically what's happening here is that as soon as the component mounts, this.props.chats_history will have some value, but it won't contain the actual list of values yet. At some point, this.props.chats_history will be loaded, and this will trigger the component to update.
componentDidUpdate is triggered every time either this.props or this.state is updated. The arguments prevProps and prevState that you see in my code are references to the values of this.props and this.state from immediately before the update occurred that triggered componentDidUpdate.
componentDidUpdate will be triggered many times, and you want to execute your sort function only when it's the loading of this.props.chats_history that has triggered it. To do this, you compare (with _.isEqual) prevProps.chats_history with this.props.chats_history. Is they're not equal, this means that this.props.chats_history was just modified (in this case, loaded), so you call sort only under these circumstances.
The reason I use _.isEqual from the lodash library is that if I did a == or === comparison, it would always return true because this.props.chats_history is an array, and it would therefore be comparing the references rather than the contents of the array. If you use _.isEqual it does a deep comparison and returns true only if each element of this.props.chats_history is equal to each element of prevProps.chats_history.
Since you then call this.setState(), componentDidUpdate will be called again, but the if block will return false and not run the sort code again.
Does that make sense?
You can use getDerivedStateFromProps instead.
static getDerivedStateFromProps(props, state) {
const sortedChat = props.profile.chats_history?[...props.profile.chats_history].sort((a,b)=>{/*logic*/}):[]
return { sortedChat };
}
You can optimize your renders by comparing the current data in state and received data in props. That depends on your data again. Personally, I'd keep a timestamp in profile.chats and only update the state when timestamp changes. Also, sort changes the original array order. So, clone before you sort like I did above.

Why does the first iteration of my update to an array in state does not appear? It will only appear after the second iteration?

I have an array postArray defined in state on Main.js.
this.state ={
step: 1,
// welcome
qNumber:1,
accountNumber:'',
amount:'',
txNumber:1,
postArray : []
}
I also have a function on Main.js which inserts new array element into postArray:
insertTx =() => {
// save transaction to array state
// create copy of the array
const copyPostArray = Object.assign([],this.state.postArray)
// insert one element into the array
copyPostArray.push({
txNumber: this.state.txNumber+"-"+this.state.accountNumber,
qNumber : this.state.qNumber,
accountNumber : this.state.accountNumber,
amount : this.state.amount
})
// save the values back to array state
this.setState({
postArray:copyPostArray
})
console.log(this.state.postArray)
console.log(this.state.txNumber)
console.log(this.state.qNumber)
console.log(this.state.accountNumber)
console.log(this.state.amount)
}
On CashDeposit.js, postArray is being updated whenever I call InsertTx function below:
continue = e => {
e.preventDefault();
this.props.nextStep();
//increment the txNumber
// this.props.incTxNumber();
this.props.insertTx();
Viewing the postArray on the console.log, it shows an empty array on first iteration. But for the second iteration, it will show the value for the first, on the third iteration will show value for the second and so on. Why does it not update current values?
setState does not happen right away. The state will always be the same values until the next render happens. If you update state, then in the same cycle reference state, you will get the old state. This would make it appear that you are one behind if you run something like:
this.setState(newValues)
console.log(this.state) // old values
Make sure that when you are referencing state you don't rely on a setState from another function. This is where hooks and useEffect come in handy.
The issue you're seeing is caused by the fact that setState does not set the state immediately, you can think of it like an asynchronous operation. So when you try to log the state values, you are getting old values because the state hasn't changed yet.
In order to get access to the new state value, you can pass a callback to setState as a second parameter: this.setState(newState, updatedState => console.log(updatedState))
This is because setState() does not immediately update state. You will not see the updated state until the next time render() is called. Because of how React reconciles, this is pretty fast, because React won't try to build the DOM until all the setState() calls have been shaken out. But it also means that, while you can't see the new state immediately in the console, you can rest assured that you will see it eventually, before it appears in the browser.
It does, however, mean you need to be sure you've got your initial state condition handled in your code. If you don't set up your state in your constructor, you'll have at least one go-around where you'll need to render without undefined state throwing errors, for example.

useState React hook always returning initial value

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

How to assign a prop value to a two state in react

I have searched for this text but none fixed my situation. my condition is I have one prop value I need to set this value to 2 state variable.one is direct assignment one is indirect. now due to async nature of set state or unknown reason before setting that value child got rendered. now how should I control this behavior?
this is my componentWillReceiveprops of child
componentWillReceiveProps(nextProps) {
let lastManualFlag =[...this.state.lastManualFlag];
console.log('before',lastManualFlag,nextProps.groupBy.length);
for(var i=0;i<nextProps.groupBy.length;i++){
lastManualFlag[i]=false;
}
console.log("after",lastManualFlag)
this.setState({lasManualProg:lastManualFlag});
this.setState({ groupBy: nextProps.groupBy});
}
this is my render method
render() {
console.log('this.state',this.state);
if (!this.props.groupBy) return null;
when I saw console my lastManualFlag is still size 0 array
You have two calls to set state
how about do it in one call like:
this.setState({...state,lastManualProg:lastManualFlag,groupBy: nextProps.groupBy});

Resources