Learning destructuring and facing problem with this situation. I have this structure -
myObj{
key1: {prop1: true, prop2: false},
key2: {prop1: false, prop2: false}
}
MyObj can have multiple keys whose names are set dynamically but all those are having same set of properties, say prop1 and prop2. I want to destructure to get prop1 and prop2 without knowing real name of key1 or key2.
const { prop1, prop2 } = myObj[someVar]; // someVar can be key1 or key2
The only thing I can think of is this, if this is not what you want please comment, I will delete it
let myObj = {
key1: {prop1: true, prop2: false},
key2: {prop1: false, prop2: false}
}
Object.values(myObj).forEach((item)=>{
const {prop1, prop2} = item;
console.log('prop1: ', prop1 );
console.log('prop2: ', prop2 );
});
Answering my own question. I accepted kiranvj's solution but later realized following format is not wrong as well,
myObj[someVar].prop1; // or myObj[someVar].prop2;
// or
const { prop1, prop2 } = myObj[someVar];
Wrong thing I was doing was with inline if statement, later creating with prop1 and prop2. That was not giving correct result. And I thought I am not destructuring it right. Adding conditional statements as well. (Though not directly related to post but adding extra information for react-newbies like me.)
// this failed to give result
{ prop1 &&
((prop2 && <div>{allTrue}</div>))}
// simplified version gives correct result
{ prop1 && prop2 && <div>{allTrue}</div> }
It looks like the question has already been answered sufficiently, but I wanted to add some advice on this.
Unless you're working with a strongly typed version of JavaScript (e.g., Elm or TypeScript), I think it's a really bad idea to destructure an object's nested properties because it would produce type errors if the base property didn't exist beforehand (key1). This would potentially cause the app to fail.
If you're working in a strongly typed version of JavaScript, then the type enforcement would potentially error out at compile-time instead of run-time.
Single-layered destructuring is fine in my opinion though, because the worst that can happen is the assigned variable would become undefined.
Related
I'm trying to clarify some confusion I have about boolean props in React.
Suppose a have MyComponent with several boolean props prop1, prop2...
First: it seems that boolean props are like just others: you can define default values, either in defaultProps or in destructuring params:
const MyComponent = ({ prop1, prop2 }) => (
...
);
MyComponent.defaultProps = {
prop1: false,
prop2: true,
}
Or (equivalently... I think)
const MyComponent = ({ prop1 = false, prop2 = true }) => (
...
)
What's not clear is how to pass values. The natural "React style", again, seems to be
<MyComponent prop1={ BOOLEAN_EXPRESION1 } prop2={ BOOLEAN_EXPRESION2 } />
... including the static literals (false/true).
However, it's also stated that the correct (recommended?) way to pass boolean properties is presence/absence of the attribute, as in HTML5.
So that, instead of <MyComponent prop1={true} prop2={false} />, one should write <MyComponent prop1 />.
My questions are:
What is the correct way of passing boolean props? Are both acceptable?
In case HTML5 style is the recommended (or correct) way, how would one deal with dynamic values?
In case HTML5 style is acceptable, what about default values?
In the example above (where prop2 is true by default), if I write <MyComponent />, what value would prop2 get?
Edited: To the accepted answer, I'd like to add my own tip: to play along nicely with the HTML5 style, design your boolean props so that they are by default false. Put in other way: a boolean prop that defaults to true should be considered an antipattern.
Forenote:
Let's just think this differently and disregard rules established by HTML5 and focusing only on JSX. JSX has exactly two ways of passing true, <MyComponent prop /> and <MyComponent prop={true} /> and exactly one way of passing false <MyComponent prop={false} />. I do agree this is an oddity distinguishing between HTML5 and JSX, but JSX is a syntax extension to JavaScript and not HTML so it does not need to conform to any of the rules of HTML.
From the JSX docs:
This funny tag syntax is neither a string nor HTML.
FYI, all rules and behavior of JSX is listed in React's JSX docs, and it includes how defaulting a prop to true works. Important note: these docs do not inherit anything from HTML and shouldn't be compared with HTML specs either.
Answers:
What is the correct way of passing boolean props?
Passing an explicit true in JSX:
There are exactly two ways to pass an explicit true: passing true and defaulting a prop to true:
<MyComponent prop={true} />
<MyComponent prop />
Note: As stated in the docs, JSX's behavior of defaulting a prop to true is just an added feature that matches with HTML5's boolean attributes behavior.
Passing an explicit false in JSX:
There is exactly one way to pass an explicit false: passing false
<MyComponent prop={false} />
Note: This is where JSX's behavior differ from HTML5's boolean attributes behavior. There is not such thing as defaulting to false in JSX; it is only applicable for passing an explicit true. In contrast to HTML5, if you do not pass anything, you're really passing undefined, and your defined default values will be used instead. This is contrary to the HTML5 specs which would say it's false. Refer to the CodeSandbox link in the answer to #3 for this behavior.
Passing a boolean variable/expression in JSX:
Pass the variable or an expression to the prop:
// via variable
const foo = true;
<MyComponent prop={foo} />
const bar = false;
<MyComponent prop={bar} />
// via expression
<MyComponent prop={Math.random() > 0.5} />
Are both acceptable?
Referring to the way of passing an explicit true vs defaulting a prop to true, they are both acceptable in terms of compiling JSX. However, if you need consistency in a codebase for adhering to a certain style, add the ESLint rule jsx-boolean-value. I personally use the Airbnb JavaScript style which turns that rule on. The guide emphasizes on readability, and so it decided for omitting the explicit true.
In case HTML5 style is the recommended (or correct) way , how would one deal with dynamic values?
Do not use HTML5 style (defaulting props to true) for dynamic values (variables/expressions); use the React way of passing props (explicitly assigning prop values). See above for how to pass those.
Furthermore, in case HTML5 style is acceptable, what about the default values? In the example above (where prop1,prop2 are resp. false/true by default) what would give?
Here are the final values for prop1 and prop2 passed respectively:
MyComponent.defaultProps = {
prop1: false,
prop2: true,
}
<MyComponent prop1 />
// prop1 == true
// prop2 == true // defaulted to true
<MyComponent prop1={true} prop2={false} />
<MyComponent prop1 prop2={false} />
// prop1 == true
// prop2 == false
Here is the CodeSandbox link: https://codesandbox.io/s/elated-davinci-izut1?fontsize=14&hidenavigation=1&theme=dark
Note: Not adding an attribute and then not passing a value (such as prop2 in the first example) is the same as passing undefined unlike passing an explicit false for the second example. Therefore, prop2 got defaulted to true.
Let me tell you about passing boolean values
HTML5 Style
<MyComponent
prop2 // equivalent prop2={true}, works only for static true values
prop1={false} // You can't do something like that for false though
/>
// example
<Select
multiSelect // You won't be changing this value throughout the whole life cycle
/>
// They will however transpiled to
React.createElement(MyComponent, {prop1: false, props2: true}, null)
// So passing true isn't necessarily a must
Default Props
This method isn't any different for boolean than other types
<MyComponent
title={title} // typeof title = string | undefined | null
bold={bold} // typeof bold = boolean | undefined | null
/>
// In case title being undefined or null,
// React will take the defaultProps
defaultProps are equivalent to
static defaultProps = {
title: 'Hello World',
bold: true
}
props = {
title: props.title ?? 'Hello World',
bold: props.bold ?? true
}
// Or in function, it's much simpler and familiar
// Same as old C, if the props are undefined or null,
// default values will be taken, this is plain 'ol JS
const MyComponent = ({title = 'Hello World', bold = true}) => {
// boop
}
So to summarize and answer your question
What is the correct way of passing boolean props? Are both acceptable?
Yes, both are acceptable. React is un-opinionated, It depends upon which style you're following, AirBnB recommends you to use HTML5 style for passing static true values, while old TypeScript screams at you to change it to props2={true}.
In case, HTML5 style is the recommended (or correct) way, how would one deal with dynamic values?
HTML5 is applicable only for static true values. There's simply no way for you to use dynamic boolean values in HTML5 style.
Furthermore, in case HTML5 style is acceptable, what about the default values? In the example above (where prop2 is true by default), if I write <MyComponent />, what would happen?
<MyComponent /> will be transpiled to React.createElement(MyComponent, {}, null), meaning prop1 and prop2 will be undefined. Given that the default values for prop1 is false while prop2 is true. Their respective default values will be taken.
Depends on use case. Ever heard of separation of concerns? The same principle apply here. Handle it where it makes sense to handle. You can use variables in a component instead of defaultProps (the React way). If its a switch pass it down from parent.
I'm getting the following error:
Parsing error: Unexpected token, expected ","
In my functional component (not class) I have:
const [ ministries, setMinistries ] = useState({
options: '',
selected: ''
});
later in another method I try to update ministries by doing the following:
let opts = [1, 2, 3, 4];
setMinistries({
ministries.selected: opts
})
Assuming ministries is the object and selected is in ministries I would expect dot notation.
ministries.selected: opts to work.
What am I doing wrong?
Please, be aware that the useState updater overwrite a previous state with a new one and it does not perform any merging.
Instead, it requires you to pass the complete state each time.
However, that's not the case with this.setState in a class component.
That's something that, to my advice, is important to remember to avoid subtle undesired behavior.
So, the correct way to update your state would be:
setMinistries(prevMinistries => ({
...prevMinistries,
selected: opts
}));
You can't do ministries.selected as an object key.
You can just do:
setMinistries({
selected: opts,
});
setMinistries({
ministries.selected: opts
})
This is bad syntax. You call the property of the object within an object.
Try
setMinistries({
ministries: {
selected: opts
}
})
or
setMinistries({
selected: opts
})
Given a reducer example like the following
_({
expandAbility: (state, a: { which: string }) => ({
...state,
report: state.report && {
waitingForIt: false,
content: state.report.content && {
...state.report.content,
interaction: {
expandAbilities: !state.report.content.interaction.expandAbilities.contains(a.which)
? state.report.content.interaction.expandAbilities.add(a.which)
: state.report.content.interaction.expandAbilities.remove(a.which)
}
}
}
}),
})
(state type given below just for question context purposes)
const initialState = {
report: undefined as
| {
waitingForIt?: boolean
content?: {
supportedFightIds: number[]
deathCut: number
reportInfo: any
deathsFull: any
deathsByPlayer: any
deathsByAbility: any
interaction: {
expandAbilities: Set<string>
}
}
}
| undefined,
error: undefined as Error | undefined
}
Is there any kind of trick or "flavor-of-the-moment" library which would allow me to write a reducer update operation like expandAbility in a shorter way? (besides maybe creating some vars to reference inner paths)
There are lots of immutable update utilities out there, check out some options at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities and see what would be the best fit for you.
For starters check out Immutability-helper or immer.
So there are two things you could do to help simplify this. The first thing I like to do is move the logic out of the reducer and instead just pass in a value and say set expandAbilities to action. expandAbilities.
The second is actually something we do at work. We use immutableJS and wrote a single reducer that handles all of our state calls because you can give it a path of the parts of state that need to be updated and the value to update it with so we extracted that out and now it is easy to say dispatch(actions.update({path: ['report', 'content', 'interaction', 'expandAbilities'], value: '123' }))
You can even expand this so you can pass in a list of values that need to be updated and even preform validations around the data.
Most of the tutorials online show that the properties inside initialState should be '' (if it is a string), like:
let defaultState = Immutable.fromJS({
viewData: {
id: '',
name: '',
alist: []
}
});
But if you than have a habit of explicitly stating what properties a certain component uses, like:
function mapStateToProps(state) {
return viewData: state.someReducer.get('data');
}
someContainer.propTypes = {
data: ImmutablePropTypes.mapContains({
id: React.PropTypes.string.isRequired,
name: React.PropTypes.string.isRequired,
aList: ImmutablePropTypes.list.isRequired
}),
};
Than the only way i will get "Failed propType" is if any of the properties got removed from the reducer and the isRequired somehow feels redundant since it will always give an empty string which is than passed on to someContainer child components.
However, if i do:
let defaultState = Immutable.fromJS({
data: {
id: null,
name: null,
aList: null
}
});
Than i will get a failed proptype if the reducer didn't populate the state, isnt this wanted? However since i rarely see others set the props to null i feels there is a good reason not to.
Also, going with the null method nesting Maps in state gets confusing since it than even viewData should be null, but then you lose the information with not showing what data it really will get:
let defaultState = Immutable.fromJS({
viewData: { // Shouldnt this be null than as well?
id: null,
name: null,
hierarchies: { // and this
one: null,
two: null
}
}
});
null is a perfectly acceptable form of default state - its an assignment value indicating that this particular property is without value.
There is a bigger question at play, which is totally dependent on your particular application (only you can answer if null makes sense), but to answer your particular question about it "being ok" - yes, it's ok.
I don't like using empty strings as default state, I find it misleading to the application. For example, my consuming components "understand" that a null value means a value has yet to be provided, whereas an empty string implies one has indeed been set, but it is simply empty (lets say, a user without a middlename - the value is ""). This allows the application deal with the current state of the app more appropriately.
Btw - its a very excellent question that isn't often explicitly explored in tutorials (as you mention).
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.