Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

feat(Button): allows button to render arbitrary component as its root #1891

Merged

Conversation

ZacharyStair
Copy link
Contributor

leverages a render prop to allow consumers to render whatever they want in place of button or anchor

closes #1890

@ZacharyStair
Copy link
Contributor Author

@asudoh @joshblack what do you think?

Copy link
Contributor

@asudoh asudoh left a comment

Choose a reason for hiding this comment

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

LGTM basically - Thank you for jumping in @ZacharyStair! Just one comment - Would you try merging the original <button> handling code and <a> handling code so your addition can fit well in there? BTW wanted to see with other reviewers on render name, in case they come up with one.

@netlify
Copy link

netlify bot commented Feb 18, 2019

Deploy preview for carbon-components-react ready!

Built with commit f6b925b

https://deploy-preview-1891--carbon-components-react.netlify.com

* Specify how the button itself should be rendered.
* Make sure to apply all props to the root node and render children appropriately
*/
render: PropTypes.func,
Copy link
Contributor Author

@ZacharyStair ZacharyStair Feb 18, 2019

Choose a reason for hiding this comment

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

this proptype isn't strictly accurate; it could be either a function (i.e. a stateless functional react component), a React.component class, or even a string. it's complicated

edit: or maybe an Object, depending on what React.forwardRef returns

Copy link
Contributor

Choose a reason for hiding this comment

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

@zachary String is a fair point. Given typeof class {} yields function, probably PropTypes.oneOfType([PropTypes.func, PropTypes.string])?

Component = 'a';
}
return (
<Component
Copy link
Contributor

Choose a reason for hiding this comment

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

Cool - Seems that we need to cover disabled and type for <button>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed (sorry for the delay, i accidentally changed inputref to inputRef and was confused why all the tests were failing...)

@ZacharyStair
Copy link
Contributor Author

ZacharyStair commented Feb 18, 2019

the reason I picked render was a) it is getting to be a generally established convention across the react ecosystem (https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js#L95, https://reactjs.org/docs/render-props.html#use-render-props-for-cross-cutting-concerns) and b) it isn't specific to this component, so if other components want to do something like this they can also use render. One potential sticking point is components that use multiple different 'render props', but it seems like you'd still want to name the most common/general render prop render.

edit: i guess react-router uses both a component prop, which has to be an actual react component, or a render function that takes the props applied by the router and returns a component. It doesn't automatically mix in props to the provided component which I don't necessarily agree with

@dakahn
Copy link
Contributor

dakahn commented Feb 19, 2019

Hi there 👋. Could you talk me through a specific use case for this functionality? Thanks!

@ZacharyStair
Copy link
Contributor Author

@dakahn sure thing:

I mentioned briefly in the linked issue: We're using the Link component from react-router-dom (https://reacttraining.com/react-router/web/api/Link) that wraps and modifies the behavior of a normal <a> element, and we want to use the carbon Button component (styles, functionality, etc) but instead of passing in an href and getting a carbon-decorated <a> element we want to pass a Link and have that be decorated with the correct classes, etc.

// our code (once this pr gets merged)
import { Button } from 'carbon-components-react';
import { Link } from 'react-router-dom';
...
<Button render={Link} foo="foo" to="www.ibm.com">bar</Button>

renders

<Link className="bx--btn" foo="foo" to="www.ibm.com">bar</Link>

which renders

<a className="bx--btn" foo="foo" href="www.ibm.com">bar</a>

@dakahn
Copy link
Contributor

dakahn commented Feb 19, 2019

Oh -- sorry to make you repeat yourself. Reading it now you're quite clear! 😸

@joshblack
Copy link
Contributor

@ZacharyStair Instead of a render prop, what do you think of an as prop pattern? (Just thinking out loud)

For example:

// Element
<Button as="a" />
// Component
<Button as={Link} />

Then the internal implementation uses React.createElement(this.props.as) for the containing node type.

Copy link
Contributor

@asudoh asudoh left a comment

Choose a reason for hiding this comment

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

Just one additional comment - BTW I think as makes sense to me, too.

@@ -164,6 +173,7 @@ Button.propTypes = {
};

Button.defaultProps = {
render: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

The default prop here seems to have caused some snapshot changes - Probably we can leave it undefined?

component = 'a';
otherProps = anchorProps;
}
return React.createElement(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i think the jsx form might be clearer but either way works

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it seems that the main difference is createElement automatically forwards refs, but we get an inputRef instead of a ref anyway so that part isn't strictly relevant

Copy link
Contributor

@asudoh asudoh left a comment

Choose a reason for hiding this comment

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

LGTM 👍 - Thanks @ZacharyStair!

@joshblack
Copy link
Contributor

Forgot to include this before, but as a reference for that as pattern we have something similar in UI Shell called Link: https://github.com/IBM/carbon-components-react/blob/master/src/components/UIShell/Link.js

In this case, it uses element but I think we will change that before stable to as as noted above.

@asudoh asudoh merged commit 7323f86 into carbon-design-system:master Feb 20, 2019
@carbon-bot
Copy link

🎉 This PR is included in version 6.98.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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.

[Button] allow consumer to replace root node with arbitrary component
5 participants