Does passing objects as props interfere with componentWillReceiveProps? - reactjs

My React app needs to keep track of a configuration object with dynamic keys, so I pass it to a component like this:
<Component configuration={this.state.configuration}>
While this works, when I am in the Component's componentWillReceiveProps(nextProps) I cannot discern configuration changes, because this.props has already been updated to nextProps.
If this isn't a known issue, perhaps it has to do with the way I handle updates to configuration state in the parent? Here's how I update configuration state:
handleConfigurationChangeForKey(newOption, key) {
const configObject = this.state.configuration;
configObject[key] = newOption;
this.setState({configuration: configObject});
}
Thanks in advance for any help.

I cannot discern configuration changes, because this.props has already been updated to nextProps.
This is not true. this.props will have the current props, nextProps the upcoming ones.
The way you set the state could be the problem. Try creating a new configuration object, using Object.create or a deep copying function (such as the one provided by lodash).
const newConfig = Object.create(oldConfig)
# or
const newConfig = _.cloneDeep(oldConfig)
newConfig[key] = newValue
This way, the object won't be equal by reference to the old version. If copying brings a performance problem, you can try the Immutable.js library for your state objects.

When you're updating the config object, you're mutating it: you can't tell the difference between nextProps.configuration and this.props.configuration because they're the same object.
The way to get around this is to basically clone the original config object, mutate that, and then use setState to make configuration point to that new object.
handleConfigurationChangeForKey(newOption, key) {
const nextConfiguration = {
...this.state.configuration,
[key]: newOption
};
this.setState({ configuration: nextConfiguration });
}
Using only older language features
handleConfigurationChangeForKey(newOption, key) {
var nextConfiguration = {};
nextConfiguration[key] = newOption;
nextConfiguration = Object.assign(
{},
this.state.configuration,
nextConfiguration
);
this.setState({ configuration: nextConfiguration });
}

Related

Input element's value in react component is not getting re-rendered when the state changes

My state object is a Map
const [voucherSet, setVoucherSet] = useState(initialVoucherSet);
initialVoucherSet is map I create at the beginning of the stateless component function.
const initialVoucherSet = new Map();
activeVoucher.voucherDenominations.forEach(voucherDemonination=> {
initialVoucherSet.set(voucherDemonination, 0);
});
const [voucherSet, setVoucherSet] = useState(initialVoucherSet);
activeVoucher.voucherDenominations an array of numbers.
I have a input which triggers a function on onChange.
const handleChange = (e)=>{
const voucherDemonination = parseInt(e.target.id);
const voucherQuantity = parseInt(e.target.value);
if (voucherQuantity >= 0) { setVoucherSet(voucherSet.set(voucherDemonination, voucherQuantity)); }
}
The state object voucherSet is getting updated, but my input's value is not getting re-rendered.
Below is the input element:
<CounterInput type='number' id={voucherDemonination} onChange={handleChange} value={voucherSet.get(voucherDemonination)} />
What I already tried
I thought that it might be because I was not setting a different object to the voucherSet state variable. So I tried something a bit hacky...
const handleChange = (e)=>{
const voucherDemonination = parseInt(e.target.id);
const voucherQuantity = parseInt(e.target.value);
if (voucherQuantity >= 0) {
const tempVoucherSet = voucherSet;
tempVoucherSet.set(voucherDemonination, voucherQuantity);
setVoucherSet(tempVoucherSet);
}
}
but it still didn't work.
Where am I wrong?
Much Thanks in advance! :)
So what is happening is that the Map itself is not changing (eg. every time you update the Map, you still have a reference to the same exact Map in memory), so react is not rerendering.
This falls under the whole "immutable" thing with react. Any time a state change happens, a new object or array ow whatever should be created so that react and easily detect that something changed and thus rerender. This makes it so react doesn't have to iterate over every key in your object/array to see if anything changed (which would kill your performance).
Try this in the code which updates your map:
tempVoucherSet.set(voucherDemonination, voucherQuantity);
setVoucherSet(new Map(tempVoucherSet)); // -> notice the new Map()
This is analogous to other code you may have seen with react and state changes where new objects/arrays are created any time a new property/item is added:
setState({ ...oldState, newProperty: 'newValue' })
setState([ ...oldArray, newItem ]);
I had the same issue in the past. Set your state this way:
setVoucherSet(new Map(voucherSet.set(voucherDemonination, voucherQuantity)));
That will cause a re-render.

React how to delete properties from state

I'm trying to replace whole state or at least delete all properties from it.
Before react 16 I just called these two lines
this.state = {}
this.forceUpdate()
With update to react 16, this didn't work anymore.
Currently I have this workaround in shouldComponentUpdate Method:
for (let prop in nextState) {
if (nextState.hasOwnProperty(prop)) {
delete nextState[prop];
}
}
But this 'feels' not right. So does anybody know the right way to reset whole state with new object? As far as I tested this.setState just changes the differences and leave other properties untouched.
You can't remove properties from the state, because it internally uses a merge
nextState = Object.assign({}, nextState, partialState);
So no way of removing already present keys. Only thing you can do is set the current keys to undefined.
If you know the properties, you can set them manually.
If not, you can try this to set them all to undefined:
this.setState(
Object.keys(this.state).reduce((a, c) => {
a[c] = undefined;
return a;
}, {})
)

When changing one specific state setting, is it necessary to restate all the other ones?

Let's say, I have a state that looks as follows.
constructor(props) {
super(props);
this.state = {
setting_a: "value-1",
setting_b: "color-green"
}
}
When I change the state of a specific setting (e.g. setting_a), I don't want other settings (e.g. setting_b) to disappear. So I also specify the other settings while changing the state. (which is easy using the spread operator ...state).
this.setState( {...this.state, setting_a: "value-2"});
I noticed though, that some tutorials restate them, and others only specify the changed key-values.
Things got just a little bit more complicated since the introduction of the Component#getDerivedStateFromProps method, ( since React 16.3 ).
static getDerivedStateFromProps(props, state) {
const oldSetting = state.setting_a;
const newSetting = props.setting_a;
if (oldSetting !== newSetting) {
// this is a very similar situation.
return ({ ...state, state.setting_a: props.setting_a});
}
return null;
}
Again, in the above example, I add all previous settings (i.e. ...state), because I don't want the other settings to be removed.
In both these cases, the same question: do I need to specifically repeat values which are already in the state ? Or are the states always merged incrementally, without removing ?
You don't need to copy the state (using spread operator or any idea) when updating the state with setState. The setState method updates the required state only:
this.setState( {setting_a: "value-2"});
So, now you will still get:
state = {
setting_a: "value-2",
setting_b: "color-green"
}
Similarly, it works like that when you return the object in getDerivedStateFromProps. The returned value is applied in the state without mutation.
You only need to copy the state when you want to update the property of state. For eg.:
// initial state
this.state = {
settings: {
a: 'value-1',
b: 'color-green'
}
}
Now, we have a and b property in settings state. So now, if you wanted to update the a, then you'll need to copy the settings:
this.setState((state) => ({settings: {...state.settings, a: 'value-2' } }))
The preceding example is with settings object. You can think similar with array of state. You may just do a google search for how to update the array without mutation?
It depends.
In your first case you could do:
this.setState( prevState => {
prevState.setting_a = "value-2";
return prevState
});
Or just go with:
this.setState({ setting_a: "value-2" });
As per React Docs State Updates are Merged.

Redux key based array not triggering new props when added to 2nd time

I am dealing with Proposals and locations.
A location can have multiple proposals.
The component is passed a location object (below as passedProps) to show all the proposals for this location :
<Proposals location={ location } key={location.id}/>
Here is are my redux props :
const mapStateToProps = (state , passedProps) => {
return {
proposals : state.propertyProposals[passedProps.location.id]
};
};
when adding a new proposal, I want to store their ids by location, so in a addition to storing the proposal, I am storing their Ids, in an array keyed by location Id.
The first proposal added and refreshed with new props just fine, however even though the second one is successfully pushed to the array, this is not triggering new props, so it does not "refresh" -- If I leave the route and come back I see the new 2nd proposal (which did not show the first time)
Here is the PROPOSAL_CREATE action for a new Proposal.
type :'PROPOSAL_CREATE',
payload :
{'e7ef104e-19ed-acc8-7db5-8f13839faae3' : {
id : 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
here is the case which handles it :
case 'PROPOSAL_CREATE':
{
const proposal = Object.values(action.payload)[0];
const locationId = proposal.locationId;
let newState = {...state}
if (locationId in newState) {
newState[locationId].push(proposal.id)
} else {
newState[locationId] = [proposal.id]
}
return newState
}
Is there an obvious reason I am not seeing the change in the component for the second entry?
Thanks
There is one issue here. Your store state is not immutable. You have used below line to make a copy:
let newState = {...state}
Here it does make copy of object but it's shallow copy, hence your array object in newState and state have the same reference. That's why redux doesn't identify the change in store and hence props are not updated in sequence.
You can clone your state by below methods:
let newState = JSON.parse(JSON.stringify(state));
OR if you use jQuery then:
let newState = $.extend(true, {}, state);
I think this will surely fix your issue.
Based on your reducer logic i think that you did not specify action type.
The one of the redux conventions is the action recognition based on type property.
I bet that you forgot to specify that property.
var properAction = {
type: 'PROPOSAL_CREATE',
payload: {
{
'e7ef104e-19ed-acc8-7db5-8f13839faae3': {
id: 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
}
I would recommend you to write action creators it will reduce your place for typos like that.
Cheers!
2 things:
I forgot that Arrays of an original object are still by reference. So even after
let newState = {...state}
newState[locationId]
has the same reference as
state[locationId]
As a result my original statement was mutating the original state, not creating a newState
and so
newState[locationId].push(proposal.id)
needed to be
newState[locationId] = state[locationId].concat(proposal.id);
or es6
newState[locationId] = [ ...state[locationId] , proposal.id ] ;

Change state when properties change and first mount on React - Missing function?

I have come across a problem about states based on properties.
The scenario
I have a Component parent which creates passes a property to a child component.
The Child component reacts according to the property received.
In React the "only" proper way to change the state of a component is using the functions componentWillMount or componentDidMount and componentWillReceiveProps as far as I've seen (among others, but let's focus on these ones, because getInitialState is just executed once).
My problem/Question
If I receive a new property from the parent and I want to change the state, only the function componentWillReceiveProps will be executed and will allowed me to execute setState. Render does not allow to setStatus.
What if I want to set the state on the beginning and the time it receives a new property?
So I have to set it on getInitialState or componentWillMount/componentDidMount. Then you have to change the state depending on the properties using componentWillReceiveProps.
This is a problem when your state highly depends from your properties, which is almost always. Which can become silly because you have to repeat the states you want to update according to the new property.
My solution
I have created a new method that it's called on componentWillMount and on componentWillReceiveProps. I have not found any method been called after a property has been updated before render and also the first time the Component is mounted. Then there would not be a need to do this silly workaround.
Anyway, here the question: is not there any better option to update the state when a new property is received or changed?
/*...*/
/**
* To be called before mounted and before updating props
* #param props
*/
prepareComponentState: function (props) {
var usedProps = props || this.props;
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === usedProps.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
componentWillMount: function () {
this.prepareComponentState();
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
/*...*/
I feel a bit stupid, I guess I'm loosing something...
I guess there is another solution to solve this.
And yeah, I already know about this:
https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
I've found that this pattern is usually not very necessary. In the general case (not always), I've found that setting state based on changed properties is a bit of an anti-pattern; instead, simply derive the necessary local state at render time.
render: function() {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === this.props.currentQuestion.id;
});
return ...; // use currentResponses instead of this.state.currentResponses
}
However, in some cases, it can make sense to cache this data (e.g. maybe calculating it is prohibitively expensive), or you just need to know when the props are set/changed for some other reason. In that case, I would use basically the pattern you've written in your question.
If you really don't like typing it out, you could formalize this new method as a mixin. For example:
var PropsSetOrChangeMixin = {
componentWillMount: function() {
this.onPropsSetOrChange(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.onPropsSetOrChange(nextProps);
}
};
React.createClass({
mixins: [PropsSetOrChangeMixin],
onPropsSetOrChange: function(props) {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
// ...
});
Of course, if you're using class-based React components, you'd need to find some alternative solution (e.g. inheritance, or custom JS mixins) since they don't get React-style mixins right now.
(For what it's worth, I think the code is much clearer using the explicit methods; I'd probably write it like this:)
componentWillMount: function () {
this.prepareComponentState(this.props);
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
prepareComponentState: function (props) {
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},

Resources