Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

fix(factories): handle ReactElement as primitive #1513

Merged
merged 24 commits into from
Jul 12, 2019
Merged

Conversation

layershifter
Copy link
Member

@layershifter layershifter commented Jun 18, 2019

BREAKING CHANGES

This PR changes how React.Elements are handled in shorthands, this change affects all components. To replace elements consider to use render() callback.

Before

React.Element replaced matching slot.

<Attachment header={<span>Document.docx</span>} />
<div class="ui-attachment" tabindex="0" data-is-focusable="true">
  <div><span>Document.docx</span></div>
</div>
<Button icon={<Icon className="bar" name="book" />} primary />

After

React.Element will be inside matching slot.

<Attachment header={<span>Document.docx</span>} />
// now these lines are equal
<Attachment header={{ content: <span>Document.docx</span> }} />
<div class="ui-attachment" tabindex="0" data-is-focusable="true">
  <div><span class="ui-text"><span>Document.docx</span></span></div>
</div>

To keep existing behavior with replacement consider to use Render Callback to completely replace contents of slot. However, in the most case enough to use Object Shorthand.

// use object shorthand
<Button icon={{ className: 'bar', name: 'book' }} primary />
// or render callback
<Button
  icon={render =>
    render(null, (ComponentType, props) => <Icon className="foo" name="book" {...props} />)
  }
  primary
/>

See Shorthand props docs for more details.


Problem

<Popup content='Foo' /> // it's cool, I can set message 😎 
<Popup content={<i>Foo</i>} /> // layout is broken, WTF? 💣

Our component factories handles primitives (string, number) and React.Elements in different ways. This issue comes from Semantic UI React where it was a single way to modify rendered element in matching slot. But, we have render() callback that covers that purpose.

The most common use case is over-complicated and disguised from consumer:

<Popup content={{ content: <i>Foo</i>}} />
// I want just pass an element and I have to use `{ content: <i />}` 😭 

We can't always apply props properly to passed element, it's a black box for us:

<Popup content={<Foo />} />
// will `Foo` handle `styles`, `pointerRef` or any other props? 🤔 

Solution

When passing JSX, it should be a child of the slot component, this is the more intuitive behavior:

<Popup content='Foo' />
<Popup content={<span>Foo</span>} /> // will have a <span> in DOM tree, but look will be same

I.e. we will handle React.Element in the same way as string, so the approach will be consistent.

Shorthand without JSX

Shorthands like icons and images can't render children and have mapping to other props than content. That's why we should handle these cases separately and render null in that case. Factories were updated to handle a new allowJSX param in config.

@codecov
Copy link

codecov bot commented Jun 19, 2019

Codecov Report

Merging #1513 into master will decrease coverage by 0.01%.
The diff coverage is 82.05%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1513      +/-   ##
==========================================
- Coverage   71.24%   71.23%   -0.02%     
==========================================
  Files         851      851              
  Lines        7021     7032      +11     
  Branches     1999     2004       +5     
==========================================
+ Hits         5002     5009       +7     
- Misses       2013     2017       +4     
  Partials        6        6
Impacted Files Coverage Δ
packages/react/src/components/Input/Input.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Menu/MenuItem.tsx 34.04% <ø> (ø) ⬆️
...ackages/react/src/components/Checkbox/Checkbox.tsx 80% <ø> (ø) ⬆️
...ackages/react/src/components/Reaction/Reaction.tsx 81.81% <ø> (ø) ⬆️
packages/react/src/components/Status/Status.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Button/Button.tsx 80% <ø> (ø) ⬆️
packages/react/src/components/Label/Label.tsx 95.23% <ø> (ø) ⬆️
...kages/react/src/components/Toolbar/ToolbarItem.tsx 48.14% <ø> (ø) ⬆️
...ges/react/src/components/Dropdown/DropdownItem.tsx 71.42% <ø> (ø) ⬆️
...react/src/components/RadioGroup/RadioGroupItem.tsx 85.71% <ø> (ø) ⬆️
... and 9 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e8ce890...afd4afb. Read the comment docs.

},
overrideProps: props,
}),
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still disappointed with this.

As I see we used our own components in the wrong way, I expect that we can reuse Image and Icon components, but should we reuse Label component in this case? Not sure 🌵

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only place where we implicitly pass shorthand of a shorthand? This will be issue in every case we have like this. Meaning, we have shorthand for something that is shorthand in another element..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this only in Dropdown components. I feel that it will be easier to have some custom components instead of this composition

@@ -441,7 +441,7 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
const popupContentAttributes =
accessibility.focusTrap || accessibility.autoFocus ? {} : popupWrapperAttributes

const popupContent = Popup.Content.create(content, {
const popupContent = Popup.Content.create(content || {}, {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we will pass false or null to this slot we will render nothing and it will break Popper and Ref components that expects children. I want to consider refactoring of this place more deeply.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to create a separate issue for that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1630 👍

@layershifter layershifter force-pushed the fix/factories-jsx branch 2 times, most recently from 64f6d26 to d63151f Compare June 24, 2019 09:36
@@ -62,9 +62,9 @@ class Label extends UIComponent<WithAsProp<LabelProps>, any> {
static propTypes = {
...commonPropTypes.createCommon({ color: true }),
circular: PropTypes.bool,
icon: customPropTypes.itemShorthand,
icon: customPropTypes.itemShorthandWithoutJSX,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should rethink how we create these custom prop types - the user here must know that the image and icon are this special type...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please provide more info? We already have a custom propTypes for them...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was, the user must know that the Icon and Image have different type then the other shorthands, and this is very prone to mistake in the future... Maybe we can make the component type param to the item shorthand and have one centralized place where this logic will be implemented. Other then this don't have any better idea.. What do you think?

@vercel vercel bot temporarily deployed to staging July 12, 2019 12:01 Inactive
/>
```

### Customizing rendered tree
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add new use case now - completely replace slot content.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the example in this section does required thing. I removed a paragraph that doesn't have any sense now, 1fbc89a

Co-Authored-By: Miroslav Stastny <mistastn@microsoft.com>
@vercel vercel bot temporarily deployed to staging July 12, 2019 13:20 Inactive
Co-Authored-By: Miroslav Stastny <mistastn@microsoft.com>
@vercel vercel bot temporarily deployed to staging July 12, 2019 13:22 Inactive
yarn.lock Outdated
@@ -9,7 +9,27 @@
dependencies:
"@babel/highlight" "^7.0.0"

"@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@^7.3.4":
"@babel/core@^7.1.0", "@babel/core@^7.3.4":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check again why we have changes in the yarn.lock and if we have new dependencies that require OSS scan?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted these changes because I don't why they happened 🤔 There are no any changes in any package.json

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants