I've a react component like:
ReactDOM.render(<someReactComponent someObjectParam={ 'key1': 'value1', 'key2': 'value2'} />, document.getElementById('someDivID'));
I'm sure the someObjectParam will have key1 and I want to make 'key2' as optional So, In my react component I tried something like:
var someReactComponent = React.createClass({
propTypes: {
someObjectParam: React.PropTypes.object.isRequired,
},
getDefaultProps: function() {
return {
//even tried someObjectParam['key2']:''
someObjectParam.key2: ''
};
}
render: function () {.....}
});
But I syntax error in getDefaultProps. Is there any way to define it properly?
P.S: I know a work around to do something like this.props.someObjectParam.key2 || '' in render function or set key1 and key2 as different props but I'm after a more declarative way of doing it and I can't define my whole object as default value for some other logic I'm doing.
Any help is greatly appreciated.
Since the property is an object you have to return an object, but you can certainly return an object with just a specific key populated:
getDefaultProps: function() {
return {
someObjectParam: { key1: "default" }
}
}
I'm sure the someObjectParam will have key1 and I want to make 'key2'
as optional
If this is what you want to do than you really want to make key1 and key2 as separate properties, not a single property. There's no way to partially fill a default property.
You could apply some default logic in your constructor so you don't have to worry about it from your render function:
constructor(props) {
props.objectParam.key2 = props.objectParam.key2 || "default";
super(props);
}
If the property can be updated you'll need this same logic in componentWillUpdate as well. At this point I would say it isn't really worth it, just deal with it in your render function.
This question is quite old, but I found it today researching this issue so here goes my answer.
A more "declarative" pattern is to merge someObjectProp into someObjectDefaults, like:
const defaults = { key2: "key 2 default value" }
const someObject = Object.assign({}, defaults, this.props.someObjectProp)
// or a nicer, ES6 syntax:
const someObject = { ...defaults, ...this.props.someObjectProp }
then use someObject instead of this.props.someObjectProp from there on
React's defaultProps does not merge when the prop is defined so you can't set "deep defaults" there.
Related
The componentWillReceiveProps is becoming deprecated, however, I am unclear as to how to migrate away from it. For example, a simplified version of my current looks something like this:
import Reorder, {reorder, reorderImmutale, reorderFromTo, reorderFromToImmutable} from 'react-reorder'
class ObjectsArea extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.objects ? this.props.objects.items : []
};
}
componentWillReceiveProps(nextProps){
//May have to do a deep compare between nextProps.items and current items?
if (nextProps.objects){
this.setState({items: this.nextProps.objects.items})
}
}
onReorder (event, previousIndex, nextIndex, fromId, toId) {
let new_items = reorder(this.state.items, previousIndex, nextIndex)
this.setState({
items: new_items
});
//call to parent function
}
render(){
orderable_items = <Reorder reorderId="objects" onReorder={this.onReorder.bind(this)}>
{
this.state.items.map(item => (
<div key={item.id}>
{item.text}
</div>
))
}
</Reorder>
return (
<div>{orderable_items}</div>
)
}
My requirements:
Sometimes there will be no objects property (there isn't one on initial load)
When there is an objects property a sortable/draggable list is created using the react-reorder component
When items in the list are dragged to be rearranged the onReorder function is called.
The onReorder function should do two things: update the list on the screen, call a parent function passed in from props.
Currently all of this will work with componentWillReceiveProps, however, what is the proper way to migrate away from componentWillReceiveProps based on the above requirements?
While Tolsee's answer is perfectly correct it is also worth mentioning that the react docs suggest removing derived state (state that is calculated based on props) altogether. There is a great article here that is a great read in my opinion.
Your example fits the Anti-pattern: Unconditionally copying props to state example perfectly.
Without knowing your environment I cannot recommend a solution certainly, but to me it looks like you will be able to use the Fully controlled component example.
In that case, you'd need to lift your state up, simply use objects.items to render your Reorder child, and during the onReorder event simply call a function that you received as a prop.
In your problem you can do.
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.objects){){
return {items: this.nextProps.objects.items};
}
else return null;
}
Please follow this post for better understanding
I'm using propTypes with React because I like how it warn me when I pass something dumb. But sometimes I misspell my prop or I forget to put it in my propTypes and it never get validated.
Is there a (standard) way to make React also validate that no extra props have been passed ?
I'm not sure whether there's a standard way, but you can certainly do a quick and dirty check using Object.keys.
var propsCount = Object.keys(this.props).length,
propTypesCount = Object.keys(this.propTypes).length;
if(propsCount === propTypesCount) {
// correct number of props have been passed
}
The only edge case you will have to watch for is props.children, as this arrives as an implicit property if you nest components/HTML inside your component.
If you want a more fine grained approach, then you'll have to pick out the keys and iterate them yourself, checking.
var passedPropNames = new Set(Object.keys(this.props)),
expectedPropNames = new Set(Object.keys(this.propTypes));
passedPropNames.forEach(function(propName) {
if(!expectedPropNames.has(propName)) {
console.warn('Not expecting a property called', propName);
}
});
expectedPropNames.forEach(function(propName) {
if(!passPropNames.has(propName)) {
console.warn('Expected a property called', propName);
}
});
This will do as you ask.
componentDidMount() {
let matchPropTypes = Object.keys(this.constructor.propTypes).every((a, index) => a === Object.keys(this.props)[index])
if (!matchPropTypes) {console.log('propTypes do not match props', Object.keys(this.constructor.propTypes), Object.keys(this.props))}
}
If I'm, using complex object Structure in React render, how can I avoid redefining that structure in getInitialState method
var UserGist = React.createClass({
getInitialState: function() {
return {
user:{
section: {
country: 'Iran'
}
},
lastGistUrl: ''
};
},
....
....
render: function() {
return (
<div>
{this.state.user.section.country}'s Amex Initial
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
Now the problem is the actual structure of the object used is pretty huge
user:{
section: {
.....
25 levels of nesting
.....{
country: 'Iran'
}
}
}
and that object is coming from backend , so how can I avoid defining the entire object structure in getInitialState()
First of all, state should be shallow. You shouldn't have deep objects as props or state.
Next, why do you even want to do this? Is it so that you don't get a bunch of "xx is not defined" errors on the initial render? If so, why don't you just declare user: {} in getInitialState, and wrap an if (!_.isEmpty(this.state.user)) condition around your render code?
Or, since the data is coming from the server, maybe it's a good thing to re-declare this structure. You can use getInitialState to create placeholder data until the real object shows up.
Also, be aware that setState only does a shallow replacement. If you change any property or sub-property of the user object, you'll need to clone it, change the property, and then call setState({user: clonedAndUpdatedUser}). Or, just call forceUpdate.
You should really just try to get your props and state to be shallow.
Good luck!
I'm playing around with ReactJs and I was wondering if its possible to work with state objects that have properties. Now I made a pseudo code example below which has a simple object, but I would like to handle cases where I have a lot of objects with properties of their own.
E.g. I would like to handle cases such as:
//Initialize object
getInitialState: function() {
return {
myObject: {
prop1: "",
prop2: ""
}
};
}
//How I would assign to a property of that object
setProp1: function(event) {
this.setState({myObject.prop1: event.target.value});
},
//Rendering the item (jsx)
<div>
<div className="custom">{myObject.prop1}</div>
<span>{myObject.prop2}<span>
</div>
Is something like the example above possible? Or do I need to attach each property to the state directly? What would be the best way to handle a scenario like this in Reactjs?
The best thing to do is to create a new object with the specified property changed. You can do this with something like Object.assign (using shims or libraries if necessary):
var newMyObj = Object.assign({}, this.state.myObject, {
prop1: event.target.value
});
this.setState({myObject: newMyObj});
You could also use the React immutability helpers to assist with updating nested structures:
var newMyObj = update(this.state.myObject, { prop1: { $set: event.target.value } });
this.setState({myObject: newMyObj});
If you're using Babel, JSX, or something that supports ES7 rest/spread property operations, you can simplify the code a bit (this is my favorite option when available because I think it reads better):
var newMyObj = {
...this.state.myObject,
prop1: event.target.value
};
this.setState({myObject: newMyObj});
You will often see examples that simply update the existing object and re-set the state, but this is not recommended:
// antipattern!
this.state.myObject.prop1 = event.target.value;
this.setState({myObject: this.state.myObject});
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
});
},