Component not updating on stateChange - reactjs

I would like the value of a TextField to be some computed value... as such, I have a controlled form, ComputedValue, with a render method that looks like this:
render() {
return <TextField
value={this.state.value}
key={this.props.id}
id={this.props.id}
label={this.props.label}
inputProps={{
readOnly: true
}}
/>;
}
the ComputedValue's state.value is set by pulling data from localStorage, doing some computation, and setting the state. That method looks like this:
computeValue() {
let computedValue="";
// split the computation string into constituent components
let splitCS = this.props.computationString.split(/([+,-,*,/,(,),^])/g);
// replace all instaces of questionID's with their value
for (let i = 0; i < splitCS.length; i++) {
if (splitCS[i] !== '+' && splitCS[i] !== '-' && splitCS[i] !== '*' && splitCS[i] !== '/' &&
splitCS[i] !== '(' && splitCS[i] !== ')' && splitCS[i] !=='^') {
// splitCS[i] is a questionID
let Q = getQuestionDataFromLSbyQuestionID(splitCS[i]);
splitCS[i] = Q.value;
}
}
// check that all values returned
if(splitCS.includes("")) {
console.log("null value was returned in ComputedValue question");
} else {
// rejoin string and send to eval (TODO: replace eval)
let finalComputeString = splitCS.join('')
computedValue = eval(finalComputeString);
}
// save value in state
this.setState({
value: computedValue
}, () => {
this.props.stateChangeHandler(this);
this.render();
}
);
return computedValue;
}
This component is leaf in a tree of questions; as their values change (my app syncs LS and the head parent's state) I'd like the computed value to change.
Here are my two issues:
1) Given I call setState inside the computeValue function... I'm unable to call it from within the render function. However, if I only call it from the componentWillMount lifecycle function, it computeValue only gets called once (upon mounting) and then not again. If I call it from the componentWillUpdate, I get an infinite loop. I can put a stop in this loop by comparing nextStates and what not... but that doesn't seem like the best way.
2) renders are not triggered by an update of the parent's state.. including trying a 'forceUpdate') (though they do appear to be triggered by hovering over the mouse and various other UI things).

Given I call setState inside the computeValue function... I'm unable
to call it from within the render function.
I would like you to elaborate or clarify this statment.
However, if I only call it from the componentWillMount lifecycle
function, it computeValue only gets called once (upon mounting) and
then not again.
Yes componentWillMount is a lifecycle hook that is called only once when the component is going to be added in the tree.
If I call it from the componentWillUpdate, I get an infinite loop.
This lifecycle hook is called before your component gets updated. So if you set your state here it will again call this function and it will make a infinite loop.
I can put a stop in this loop by comparing nextStates and what not...
but that doesn't seem like the best way.
You can always do the comparing and update the state. There is nothing like a wrong way in doing so.
renders are not triggered by an update of the parent's state..
including trying a 'forceUpdate')
You should never call render explicitly. It is not a correct practice because it hinders normal flow the react.
I would like you to test the setstate
this.setState(() => ({
value: computedValue
}), () => {
console.log("working")
this.props.stateChangeHandler(this);
}
);
The problem seems to be the () in ({ value: computedValue }). Give a try.

I think your answer is in your second observation. [renders are not triggered by an update of the parent's state]. guess what triggers render from the Parent? props
so, instead of putting your value in the state, put it in the prop, that way if the parent changes it, your component changes.
Hope this helps

Related

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

set state in a callback of an async function

I am new to React, so bear with me please. I have a component that calls another component that takes a property. This property will get it's value on a callback of a function, something like this:
render(){
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp))
this.setState({myProp: p})
});
return <MyComponent myProp={this.state.myProp}/>
}
myFunc will or will not make an API request and depending on that will call the callback sooner or later. This seems to work fine when API request is made and the callback takes longer to return. However, when the request is not needed and callback returns instantaneously (or almost) I am getting a Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
What am I doing wrong and what is the right way to approach this? Where would be the right place to put this code? Basically what I need is to re-render MyComponenent if this.state.myProp changes
You shouldn't be calling setState inside the render method, you might end up having an infinite loop.
The call to myFunc should be somewhere else (depending on the business logic you have). When the function finishes, it will update the state and then trigger a re-render so MyComponent will get the latest value.
UPDATE
I don't know which conditions will require calling myFunc again, but you can do:
state = {
myProp: null // or some other value that MyComponent can handle as a null state
}
componentDidMount () {
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp)) // This is needed only if you get null back from the callback and you don't want to perform an unnecesary state update
this.setState({myProp: p})
}
}
render(){
const { myProp } = this.state
// You can also do if (!myProp) return null
return <MyComponent myProp={myProp}/>
}

Reactjs, is setState needed here, changing value of an object in state

My question is, is it ok to not use setState in this situation or if I should, how would I go about it?
I'm creating a sportsbetting app.
On the list of games, when a user clicks on Denver Broncos -3, a bet slip is created. This also calls
setState({gameSelected:[item]}) in the Parent Component.
It stores that object and adds to it using the spread operator when more bets are clicked.
I pass down that data to betSlip.js file.
side note
Some of the data in the gameSelected object has :
risk:'',
win:'',
On betSlip.js I am looping through that data and displaying it.
Relevant code inside the loop.
<input type="text" placeholder="Risk" onChange={(e)=>this.handleBet(e,item)}/>
<input type="text" placeholder="Win" value={item.win}/>
This function is inside betSlip.js
handleBet = (e,item) =>{
let bet=e.target.value
//function to calculate moneyline bet
let calcBet =(bet)=>{
let newAmount =(100/item.juice)*bet;
return newAmount
}
item.win = calcBet(bet)
}
Yes, you should be using setState here to update item.win. Otherwise, React is not aware of the mutation on item and does not rerender. Since item is a prop, you should treat it as immutable from within betSlip.js and update item.win through an event handler in the parent component where item is maintained. Your betSlip.js component could have an onWinUpdate handler that is called within handleBet as this.props.onWinUpdate(item, calcBet(bet)). Then, the parent component can have a function handleWinUpdate(item, bet) that updates the state. I would also keep things immutable in handleWinUpdate by doing something like this:
handleWinUpdate(item, bet) {
this.setState(state => ({
gameSelected: state.gameSelected.map(i => {
if (i === item) {
return { ...i, win: bet };
} else {
return i;
}
})
}));
}
Notice that we are setting gameSelected to a new array and replacing item in that array with a new copy of item, but with a new win value. No objects are ever mutated.
Even though you don't have to use immutability here, I find it easier to reason about and it opens the door for performance gains later using things like React.PureComponent that rely on immutability.

How the componentWillReceiveProps update the props in react?

Below is some code snippets for the componentWillReceiveProps.
Here bulkUploadRptSuccess, bulkUploadRptError are the array and bulkUploadRptException is the string.
so once state updating two array and one string it is opening the popup.
its working as expected.
But now when clicking on any thing into the application open the pop up everytime.
How can I compare the conditional array check inside the componentWillReceiveProps.
How can I compare the two array value with the equal or not inside this function
Thanks,
componentWillReceiveProps = (nextProps) => {
let { OCFCheckConfig } = this.props;
let { bulkUploadRptSuccess, bulkUploadRptError, bulkUploadRptException } = OCFCheckConfig;
if (nextProps.OCFCheckConfig.bulkUploadRptSuccess.length > 0 || nextProps.OCFCheckConfig.bulkUploadRptError.length > 0) {
this.addPopupOpen();
}
}
ComponentWillReceiveProps doesn't update props. It actually receives updated props when parent re-render or you're connected to redux and your store gets updated. Anyways It is unsafe to use ComponentWillReceiveProps. Now here we have a replacement for it, getDerivedStateFromProps.

Best practice for updating newState via shouldComponentUpdate?

I've created a text input that only updates it's own state when you're typing via onChange, and then works it's way up the parent via onBlurEvent to prevent re-rendering too much of my GUI when a user is typing.
This works, but my drawback is that since the input has it's own state, if the props change because of external forces, and I want update my text input to be those new props, I'm in a bit of a pickle. From what I've been looking up, shouldComponentUpdate can create a fix, and my code works, but just because it works doesn't mean that my implementation is good.
When using react, I understand that we should not update the state by doing:
this.state.value = "This is a new value"
and instead do:
this.setState({
value: "This is a new value"
});
But what about directly changing newState which technically isn't "this.state" inside of shouldComponentUpdate?
I have the below logic:
shouldComponentUpdate(props, newState) {
if (...logic 1 ... && props.value != newState.value) {
//keep newState
return true;
}
else if (...logic 2 ... props.value != newState.value)
{
newState.value = props.value; //Right Here
return true;
}
return false;
};
That seems just as bad as (if not worse than) setting this.state to me. You should only ever be updating your state through setState, as you're running the risk of later updates overwriting your changes otherwise.
A much better solution would be to use the componentWillReceiveProps(nextProps) lifecycle hook - the docs say:
If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Which sounds exactly like what you're trying to do!
I don't know your logic, so I can't give a full example, but here's a starting point:
componentWillReceiveProps(nextProps) {
if (/* your condition */ && nextProps.value != this.state.value) {
this.setState({
value: nextProps.value
});
}
}

Resources