$30 off During Our Annual Pro Sale. View Details »

[NordicJS] Best Practices for Reusable UI Components

Mars Jullian
September 07, 2017

[NordicJS] Best Practices for Reusable UI Components

React is awesome but even good frameworks don’t prevent you from writing bad code. This talk will focus on some key principles for writing reusable UI components that will make them fun to use and keep your co-workers from going crazy.

ReactJS has been adopted by many companies around the world and encouraged Frontend engineers to think about UI's in terms of small components, as opposed to large applications. While we as a community have started writing our UI's in terms of components, we are all still learning about the best way to write those components.

What size should the components be? How much state should each component contain?

This talk puts forth some best practices learned while building a reusable UI component library for an entire engineering team. And while the talk is specific to writing reusable UI components in React, the best practices and ways of thinking about components presented in this talk can be can be applied to components written in other frameworks as well.

Talk recording: https://www.youtube.com/watch?v=rMFI1HtuFv4&index=10&list=PLGP3VO5jDf8xpaeBAhJOJaEjt1C7sE5Sf

Mars Jullian

September 07, 2017
Tweet

More Decks by Mars Jullian

Other Decks in Technology

Transcript

  1. 1
    React plus X
    Mars Jullian Senior UI Engineer
    Best practices for reusable UI components.

    View Slide

  2. 2
    Why build a UI component library?
    • 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.

    View Slide

  3. 3
    Why build a UI component library?
    • 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.

    View Slide

  4. 4
    Hi, I’m Mars.
    Senior Software Engineer @
    I like reusable UI components.

    View Slide

  5. 5
    React & Reusable UI
    components
    • JSX / no templates —> less files per component
    • Markup a function of state & props —> UI
    components can be independent of data source
    but still have full control over interactions.
    • Easily ported into different frameworks

    View Slide

  6. 6
    React & Reusable UI
    components
    • JSX / no templates —> less files per component
    • Markup a function of state & props —> UI
    components can be independent of data source
    but still have full control over interactions.
    • Easily ported into different frameworks

    View Slide

  7. 7
    React & Reusable UI
    components
    • JSX / no templates —> less files per component
    • Markup a function of state & props —> UI
    components can be independent of data source
    but still have full control over interactions.
    • Easily ported into different frameworks

    View Slide

  8. 8
    Reusable UI components should be…
    …self-sufficient
    …easy to integrate with

    View Slide

  9. 9
    Reusable UI components should be…
    …self-sufficient
    …easy to integrate with

    View Slide

  10. 10

    View Slide

  11. 11

    View Slide

  12. 12
    Reusable UI components should be…
    …self-sufficient
    …easy to integrate with

    View Slide

  13. 13
    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

    View Slide

  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;
    },
    keep track of
    currently
    selected tab
    14

    View Slide

  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;
    },
    knowing which tab is currently
    selected, means you know what
    application data / content to render

    View Slide

  16. 16
    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

    View Slide

  17. 17
    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

    View Slide

  18. 18 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.

    View Slide

  19. 19
    Reusable UI components should be…
    …self-sufficient
    …easy to integrate with

    View Slide

  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.

    View Slide

  21. 21

    View Slide

  22. 22
    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

    View Slide

  23. 23
    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

    View Slide

  24. 24
    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

    View Slide

  25. 25
    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.

    View Slide

  26. 26
    Ease of integration: Self-documentation.
    npm install gulp-react-docs

    View Slide

  27. 27
    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.

    View Slide

  28. 28

    View Slide

  29. 29
    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

    View Slide

  30. 30
    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.

    View Slide

  31. 31

    View Slide

  32. 32
    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

    View Slide

  33. 33 handleTabClick(selectedTab) {
    this.setState({ selectedTab });
    this.props.tabEvents.onClick(selectedTab);
    },
    renderTab(tab) {
    return (
    className='tab-wrapper'
    onClick={ () => this.handleTabClick(tab.name) }
    >
    { tab.name }

    );
    },
    Ease of integration: Remove backdoors.
    good

    View Slide

  34. 34
    renderTab(tab) {
    return (
    className='tab-wrapper'
    { ...this.props.tabEvents }
    >
    { tab.name }

    );
    },
    Ease of integration: Remove backdoors.
    backdoor into React
    div elements. BAD!

    View Slide

  35. 35
    Ease of integration: Fill container element.
    margin
    border
    padding
    position
    -
    -
    -
    - - -
    - - -
    -
    -
    -
    just this
    your component

    View Slide

  36. 36
    Reusable UI components should be…
    …self-sufficient
    …easy to integrate with

    View Slide

  37. 37
    Thanks, I’m @marsjosephine.
    Senior Software Engineer @
    I like reusable UI components.

    View Slide