React: use defaultProps when an attribute of an object is undefined?

Created on 9 Aug 2019  路  7Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?

Maybe a bug?

What is the current behavior?

https://codesandbox.io/s/green-snow-79z65

When you give undefined to a direct attribute, it will use the defaultProp.

When you give undefined to an attribute of an object, it'll use this undefined

What is the expected behavior?

I'm not sure but I'm waiting for using the defaultProp even for an attribute of an object?

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

react 16.9.0

Most helpful comment

I don't think it's a bad practice. It's easier for default values or for controlling when your component should rerender, that is for sure.
For me it's all about the use case:

  • If I build a simple component, I will go for flat props:
<Component x={10} y={y} /> // I prefer this
<Component dimensions={{ x: 10, y: 10 }} />
  • If I have an entity that is part of my business model and is reused throughout my application as complex object, then I'll keep it as an object:
<Widget meta={props.meta} />
  • If I have a complex component that requires a lot of properties, then I'll go for nicer API and choose
<Component
    parser={props.parser}
    printConfig={props.printConfig}
    filters={props.filters}
    pagination={props.pagination}
/>

instead of

<Component
    parserProperty1={props.parser.property1}
     /* ... */
    paginationProperty99={props.pagination.property99}
/>

Of course, you could trick a bit the last case

<Component
    { ...props.parser }
    { ...props.printConfig }
    { ...props.filters }
    { ...props.pagination }
/>

But you still end up with a lot of properties to handle inside the component, and probably lose typings

All 7 comments

Hello @kud! I don't think it's a bug. You do provide an object { attribute: undefined } for your Components object property:

 <Checker
    object={{
        attribute: undefined
    }}
/>

so technically it isn't undefined anymore, and in that case defaultProps aren't applied.

You probably could just write your own getDefaultObject(props) function with custom logic merging your default object and props:

<Checker
    object={getDefaultObject(props.object)}
/>

const defaultObject = { attribute: "attribute should be displayed too" }

function getDefaultObject(propsObject ) {
    return { ...defaultObject, ...propsObject }
}

Of course if you want automatically merge objects of any shape you'll have to use some library or write your own method for traversing both object trees and merging, which could hit perfomance and I think that is the reason why defaultProps can't do it by its own.

Deep merging objects isn't really appropriate for a library to decide. If object had multiple mutually exclusive schemas, e.g. object={{ foo: true }} or object={{ bar: 30 }}, it would be wrong for the library to force a deep merging strategy.

Correct, React does not traverse objects when calculating props with defaultProps, so this is expected behavior.

Understood! Thanks for your kind explanation! :)

I'm still on it, sorry.

I've got a question.

Do you think so that using an object as props is finally a bad practice?

I wonder if I shouldn't make flat all my props now, to have the possibility to use "correctly" the defaultProps.

I don't think it's a bad practice. It's easier for default values or for controlling when your component should rerender, that is for sure.
For me it's all about the use case:

  • If I build a simple component, I will go for flat props:
<Component x={10} y={y} /> // I prefer this
<Component dimensions={{ x: 10, y: 10 }} />
  • If I have an entity that is part of my business model and is reused throughout my application as complex object, then I'll keep it as an object:
<Widget meta={props.meta} />
  • If I have a complex component that requires a lot of properties, then I'll go for nicer API and choose
<Component
    parser={props.parser}
    printConfig={props.printConfig}
    filters={props.filters}
    pagination={props.pagination}
/>

instead of

<Component
    parserProperty1={props.parser.property1}
     /* ... */
    paginationProperty99={props.pagination.property99}
/>

Of course, you could trick a bit the last case

<Component
    { ...props.parser }
    { ...props.printConfig }
    { ...props.filters }
    { ...props.pagination }
/>

But you still end up with a lot of properties to handle inside the component, and probably lose typings

I understand your point yes, thank you for this clear explanation.

Was this page helpful?
0 / 5 - 0 ratings