[Modern Web] React + X: Best practices for reusable UI components

4dc1be409f2e104c83bbcab783d05509?s=47 Mars Jullian
September 14, 2016

[Modern Web] React + X: Best practices for reusable UI components

Slides for my talk at the Modern Web meetup at Netflix on September 14, 2016.

Recording: https://www.youtube.com/watch?v=Yy7gFgETp0o
Meetup link: http://www.meetup.com/modernweb/events/232244178/

4dc1be409f2e104c83bbcab783d05509?s=128

Mars Jullian

September 14, 2016
Tweet

Transcript

  1. 1.

    1 React plus X Mars Jullian Senior UI Engineer Best

    practices for reusable UI components.
  2. 2.

    2 Hi, I’m Mars. Senior UI Engineer @ I like

    reusable UI components (a lot).
  3. 3.

    3 The challenge. •Breaking out of the monolith —> new

    repos for teams / products -> different frameworks & build systems. •Brand & UX consistency —> Share & maintain the design & functionality across codebases easily.
  4. 5.

    5 React: No templates needed. const React = require(‘react'); const

    HelloWorld = React.createClass({ render() { const mood = 'good'; const greeting = mood === 'good' ? 'Hello' : 'Leave me alone'; return ( <div className='my-wrapper'> { `${greeting}, world!` } </div> ); } }); module.exports = HelloWorld; UI Logic Markup
  5. 6.

    6 React: Component-ize. const React = require('react'); const Hello =

    require('myComponents/Hello.jsx'); const World = require('myComponents/World.jsx'); const HelloWorld = React.createClass({ render() { return ( <div className='my-wrapper'> <Hello /> <World /> </div> ); } }); module.exports = HelloWorld; Components
  6. 7.

    7 React: Declarative framework. const React = require('react'); const HelloWorld

    = React.createClass({ render() { const greeting = this.props.mood === 'good' ? 'Hello' : 'Leave me alone'; return ( <div className='my-wrapper'> { `${greeting}, World!` } </div> ); } }); module.exports = HelloWorld; Props
  7. 8.

    8 React & Reusable UI components •JSX / no templates

    —> less files per component •State & Props —> UI components can be independent of data source but still have full control over interactions. •Easily ported into different frameworks
  8. 10.

    10

  9. 11.

    11

  10. 12.

    12 Self-sufficiency: Don’t worry about data. const Bob = React.createClass({

    propTypes: { tabs: React.PropTypes.arrayOf( React.PropTypes.shape({ key: React.PropTypes.string.isRequired, displayName: React.PropTypes.string.isRequired, content: React.PropTypes.oneOfType({ React.PropTypes.func, React.PropTypes.node }).isRequired }) ), // ... more props } }); data / content not part of component state, should come from parent application
  11. 13.

    13 Self-sufficiency: Enough state to be useful. handleTabClick(selectedTab) { this.setState({

    selectedTab }); this.props.tabEvents.onClick(selectedTab); }, renderTabContent(isOverview) { const currentTab = this.state.selectedTab; const tab = _.find(this.props.tabs, { key: currentTab }); const tabContent = tab.content; return _.isFunction(tabContent) ? tabContent() : tabContent; }, keep track of currently selected tab
  12. 14.

    14 Self-sufficiency: Enough state to be useful. handleTabClick(selectedTab) { this.setState({

    selectedTab }); this.props.tabEvents.onClick(selectedTab); }, renderTabContent(isOverview) { const currentTab = this.state.selectedTab; const tab = _.find(this.props.tabs, { key: currentTab }); const tabContent = tab.content; return _.isFunction(tabContent) ? tabContent() : tabContent; }, knowing which tab is currently selected, means you know what application data / content to render
  13. 15.

    15 Self-sufficiency: Enough state to be useful. handleTabClick(selectedTab) { this.setState({

    selectedTab }); this.props.tabEvents.onClick(selectedTab); }, renderTabContent(isOverview) { const currentTab = this.state.selectedTab; const tab = _.find(this.props.tabs, { key: currentTab }); const tabContent = tab.content; return _.isFunction(tabContent) ? tabContent() : tabContent; }, let parent application know that UI state has changed
  14. 16.

    16 Self-sufficiency: State initialization. const Bob = React.createClass({ propTypes: {

    selectedTab: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.oneOf([ TAB_OVERVIEW ]) ]) } }, componentWillReceiveProps() { // does nothing } }); parent application can initialize component in a certain state
  15. 17.

    17 const Bob = React.createClass({ propTypes: { selectedTab: React.PropTypes.oneOfType([ React.PropTypes.string,

    React.PropTypes.oneOf([ TAB_OVERVIEW ]) ]) } }, componentWillReceiveProps() { // does nothing } }); …but selected tab cannot be overridden in subsequent updates Self-sufficiency: State initialization.
  16. 18.

    18 const Bob = React.createClass({ propTypes: { cover: React.PropTypes.element.isRequired, title:

    React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element ]).isRequired, description: React.PropTypes.string.isRequired, tabs: React.PropTypes.arrayOf( React.PropTypes.shape({ key: React.PropTypes.string.isRequired, displayName: React.PropTypes.string.isRequired, content: React.PropTypes.oneOfType({ React.PropTypes.func, React.PropTypes.node }).isRequired }) ), tabEvents: React.PropTypes.shape({ onClick: React.PropTypes.func }), selectedTab: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.oneOf([ TAB_OVERVIEW ]) ]) } }); Ease of integration: Self-documentation. specify what types of props component expects
  17. 19.

    19 const Bob = React.createClass({ propTypes: { cover: React.PropTypes.element.isRequired, title:

    React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element ]).isRequired, description: React.PropTypes.string.isRequired, tabs: React.PropTypes.arrayOf( React.PropTypes.shape({ key: React.PropTypes.string.isRequired, displayName: React.PropTypes.string.isRequired, content: React.PropTypes.oneOfType({ React.PropTypes.func, React.PropTypes.node }).isRequired }) ), tabEvents: React.PropTypes.shape({ onClick: React.PropTypes.func }), selectedTab: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.oneOf([ TAB_OVERVIEW ]) ]) } }); Ease of integration: Self-documentation. specify which props are required for component to behave as expected
  18. 20.

    20 const Bob = React.createClass({ propTypes: { cover: React.PropTypes.element.isRequired, title:

    React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element ]).isRequired, description: React.PropTypes.string.isRequired, tabs: React.PropTypes.arrayOf( React.PropTypes.shape({ key: React.PropTypes.string.isRequired, displayName: React.PropTypes.string.isRequired, content: React.PropTypes.oneOfType({ React.PropTypes.func, React.PropTypes.node }).isRequired }) ), tabEvents: React.PropTypes.shape({ onClick: React.PropTypes.func }), selectedTab: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.oneOf([ TAB_OVERVIEW ]) ]) } }); Ease of integration: Self-documentation. specify that component expects tabs, and exactly what the tabs should look like
  19. 21.

    21 const Bob = React.createClass({ propTypes: { cover: React.PropTypes.element.isRequired, title:

    React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element ]).isRequired, description: React.PropTypes.string.isRequired, tabs: React.PropTypes.arrayOf( React.PropTypes.shape({ key: React.PropTypes.string.isRequired, displayName: React.PropTypes.string.isRequired, content: React.PropTypes.oneOfType({ React.PropTypes.func, React.PropTypes.node }).isRequired }) ), tabEvents: React.PropTypes.shape({ onClick: React.PropTypes.func }), selectedTab: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.oneOf([ TAB_OVERVIEW ]) ]) } }); Ease of integration: Self-documentation.
  20. 23.

    23 title: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element ]).isRequired, // titleLogoSrc: React.PropTypes.string.isRequired, //

    titleString: React.PropTypes.string.isRequired Ease of integration: Fewer props. one flexible prop vs. two differently named props
  21. 24.

    24 const TabNav = React.createClass({ propTypes: { tabs: React.PropTypes.arrayOf( React.PropTypes.shape({

    // ...tab shape }) ), // hideTabs: React.PropTypes.bool }, render() { if (!this.props.tabs || !this.props.tabs.length) { return null; } // ... } }); Ease of integration: Fewer props. decouple props by intelligently rendering tabs, instead of adding a hideTabs prop
  22. 25.

    25 handleTabClick(selectedTab) { this.setState({ selectedTab }); this.props.tabEvents.onClick(selectedTab); }, renderTab(tab) {

    return ( <div className='tab-wrapper' onClick={ () => this.handleTabClick(tab.name) } > { tab.name } </div> ); }, Ease of integration: Remove backdoors. good
  23. 26.

    26 renderTab(tab) { return ( <div className='tab-wrapper' { ...this.props.tabEvents }

    > { tab.name } </div> ); }, Ease of integration: Remove backdoors. backdoor into React div elements. BAD!
  24. 27.

    27 Ease of integration: Fill container element. margin border padding

    position - - - - - - - - - - - - just this your component
  25. 29.