dyo / dyo

Dyo is a JavaScript library for building user interfaces.

Home Page:https://dyo.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Short questions regarding defaultProps and propTypes

mcjazzyfunky opened this issue · comments

Dyo does not have support for component defaultProps - this is intended (not just forgotten), correct?
... like in future React, where they seem to deprecate defaultProps support for function components: reactjs/rfcs#107

Deprecate defaultProps on function components.

Any plans for propsTypes support in Dyo?

Frankly, I'm not a big fan of getting rid of defaultProps, as the React team seems to plan.

Of course you can handle default props the following way:

function SayHello({ name = 'stranger' }) {
  return <div>Hello, {name}!</div>
}

But in cases where you do NOT want to deconstruct the props objects (which I personally prefer in most cases when implementing non-trivial components) you have to do something like the following (which is a bit annoying, isn't it?):

function SayHello(props) {
  props = { ...SayHello.defaultProps, props }

  return <div>Hello, {props.name}!</div>
}

SayHello.defaultProps = {
  name: 'stranger'
}

But the main reason why I think defaultProps are a good thing can be seen in the following example:

https://codesandbox.io/s/j33qqxy2y

Here, it's very useful that the property align of Table.Column is set to 'left' by default, so the component Table does not have to handle with empty column align properties but instead can always use child.props.align directly as-is (=> separation of concerns).

The example looks odd. Is passing non-renderable child elements to components a React pattern?
Having a component that represents two split trees (i.e. a <column> which maps to <th><column></th><td><column></td>) seems like an anti-pattern.
Reading child.props directly also seems like an anti-pattern (though maybe in React-land it's more common)

The way I'd write the example would be to pass a prop to Table which contains column information, e.g.:

<Table columns=[{title: 'First name', field: 'firstName', align: 'right'}]>

Okay, maybe my example was not a good choice to show what I've meant.
The fact, that in the above example Table.Column does not really render itself is not important at all.
Important is that there are components that do not use all their properties inside of their own (render) function.
Some component properties may only be used by the parent component.
For example a tab component:

<Tabs>
  <Tabs.Page title="Page 1">
    This is page 1... 
  </Tabs.Page>
  <Tabs.Page title="Page 2">
    This is page 2...
  </Tabs.Page>
</Tabs>

Depending on the implementation, there is a good chance that the property title will not be used inside of Tabs.Page = props => { ... } . Tabs.Page does not use property title itself, instead it will be used by the parent component Tabs by accessing child.props.title. And there may be also other props of Tabs.Page that will only be used by the parent component Tabs. And for all those "will-actually-be-used-by-the-parent" properties we cannot provide a default value if defaultProps is no longer supported by Dyo/React. Of course there's no absolute need for defaultProps support - but I think it's a nice-to-have feature.

For example tabs in Office-UI Fabric React (different naming there: "Pivot"/"PivotItem" instead of "Tabs"/"Tabs.Page" and "linkText" instead of "title"):
https://github.com/OfficeDev/office-ui-fabric-react/blob/master/packages/office-ui-fabric-react/src/components/Pivot/Pivot.base.tsx#L213

I'll make a claim:
If the parent is the only consumer, then it should default the values, not the child.

I'd argue that this is the most correct separation of concerns (the child should not even know the property exists, and shouldn't validate it either).

@Zolmeister Frankly, I completely disagree:
createElement is just a pure function that returns a data structure. There's really nothing special about that.
<MenuItem title="About..."/> is basically the same as const createMenuItem = createElement.bind(null, MenuItem); createMenuItem({ title: 'About...' }). Here createMenuItem is also a pure function that just returns a data structure. Why is using default values fine and common for thousands of pure functions out there, but only for virtual element factories like createElement or the above createMenuItem, default values shall be an anti-pattern or even evil?

One last example from my side which hopefully shows my point eventually a bit better:
Almost all implementations that I have seen regarding menu components in React use parent/children combinations of components "Menu" and "MenuItem" (it's not necessarily the way I prefer, but this seems to be quite common in React world).
In the following demo you see a MenuBar/Menu/Item implementation (based on the "CommandBar" component of Office-UI-Fabric) where you can see that sometimes it makes completely sense that a component is never rendering itself directly (which, like said, is not the important aspect here), and that sometimes parent components read properties from their children (which is the important apect):
https://codesandbox.io/s/mqwz2nq00p

Again I think it's a nice-to-have feature that I can use defaultProps for the components Menu and Item in this example, as using default values often means, less messing around with undefined values - and nobody likes undefined values.

But I understand your point, of course (my opinion just may differ). If Dyo will skip support for defaultProps (which it seems like) this will not be big of a deal, and I am fine with that of course (especially since React - in future (it seems) - will also drop support for defaultProps in function components). Again, I do not want to waste anybody's time with further discussion, there may be more important things to talk about than this defaultProps thing 😃 - so consider this comment here as "just for the record"...

Dyo does not have support for component defaultProps - this is intended (not just forgotten), correct?

Yes.

Any plans for propsTypes support in Dyo?

No, and i suspect this will make its way into React as well.

Regarding

const { linkText, ...pivotItemProps } = pivotItem.props;

From office-ui-fabric-react/Pivot/Pivot.base.tsx#L213.

Any reason one couldn't do:

const { linkText = 'defaultValue', ...pivotItemProps } = pivotItem.props;

using default values often means, less messing around with undefined values - and nobody likes undefined values.

Yes i agree, and this was essentially the motivation behind leaving it out, as i would assume is also the case of React.

That is regardless of if you used/did not use defaultProps you'd end up paying the cost of one more cache miss for every component indiscriminately when the library always has to introspect Type.defaultProps.

Something that i hope the general direction that plain functions + hooks attempts to move away from.

This also has other limitations that i'm sure will apply to React's attempts at a 1st/2nd tier optimising compiler that might inline function components entirely in some cases as an optimisation.

The nice thing about function components in the absence of classes or at least in the shape that it is in Dyo is that they are zero-cost abstractions when compared to classes, so higher order function component are perfectly good candidates for extending a feature – for example memo is implemented as a higher order component.

So you could imagine the following could replicate defaultProps, or propTypes.

function defaultProps (type, defaults) {
	return props => type({...defaults, props})
}
function propTypes (type, props) {
	return props => PropTypes.validate(props) ? type(props) : throw 'Error!'
}

const FooWithDefaultProps = defaultProps(Foo, {align: 'left'})
const BarWithPropTypes = propTypes(Bar, {align: PropTypes.string})

Thanks for the detailed explanation and additional information.
The hint regarding that some future optimizing compilers may do some fancy stuff with the JSX expressions is really convincing. Especially for static JSX expressions defaultProps as used today could really be an anti-feature.
Okay, that helped a lot :-)

I will close this issue now, because the open questions have been answered.
Thank you very much, guys, for your time and your clarifications.