Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Component Patterns WDI version

React Component Patterns WDI version

In which I present most popular React design patterns like: Higher order Component, Function as a Child (Render Prop), Provider Component, Compound Components, and Reducer Component. I describe each pattern and discuss it's tradeoff, and also provide usage and implementation examples.

Dawid Jankowiak

March 27, 2018
Tweet

More Decks by Dawid Jankowiak

Other Decks in Programming

Transcript

  1. Higher Order Component Function as a Child (Render Function) Provider

    Component Compound Components Reducer Component
  2. const ApplicantInfoPage = ({ data }) => {...}; export default

    withFetch( ownProps => `/api/applicants/${ownProps.match.props.id}` )(ApplicantInfoPage);
  3. const withFetch = urlGetter => BaseComponent => class WithFetch extends

    React.Component { state = { isLoading: true, error: null, data: null }; static displayName = `WithFetch(${wrapDisplayName( BaseComponent )})`;
  4. componentDidMount() { axios .get(urlGetter(this.props)) .then(({ data }) => this.setState({ data,

    isLoading: false })) .catch(error => this.setState({ error, isLoading: false })); }
  5. render() { if (this.state.isLoading === true) { return <Loading />;

    } if (this.state.error !== null) { return <ErrorPanel />; } return <BaseComponent {...this.props} data={this.state.data} />; } };
  6. Lets you mix behaviours Simple to use Composable (like regular

    function composition) Applicable before use (cannot be used in render function) Refs aren’t passed through Static methods must be copied over
  7. Downshift React-Motion React-Router v4 react-virtualized Passing a function to a

    component (as a prop or children of component) that tells it what to render
  8. <Paging items={itemsArray} itemsPerPage={10} render={item => ( <div>{item.name}</div> )} />; <Paging

    items={itemsArray} itemsPerPage={10} > {item => ( <div>{item.name}</div> )} </Paging>;
  9. class Paging extends Component { … render() { const items

    = this.getItems(); return ( <div> {items.map(this.prop.render)} {this.renderButtons()} </div> ); } } class Paging extends Component { … render() { const items = this.getItems(); return ( <div> {items.map(this.prop.children)} {this.renderButtons()} </div> ); } }
  10. Used as regular components Simple to implement Dynamic in nature

    Nesting (hell?) Returning large component tree may introduce performance problems
  11. const App = () => ( <AuthenticationProvider> <div> <NavBar />

    <BrowserRouter> <Route exact path="/" component={Home} /> <Route path="/profile" component={Profile} /> <Route path="/campaigns" component={Campaigns} /> </BrowserRouter> </div> </AuthenticationProvider> );
  12. class AuthenticationProvider extends React.Component { static childContextTypes = { user:

    UserShape }; state = { isAuthenticated: false, user: null }; getChildContext() { return { user: this.state.user }; }
  13. componentDidMount() { checkCookie().then(user => this.setState({ isAuthenticated: true, user }) );

    } handleAuthentication = user => { setCookie(user); this.setState({ isAuthenticated: true, user }); };
  14. render() { if (this.state.isAuthenticated) { return React.Children.only(this.props.children); } return (

    <Authentication onAuthenticated={this.handleAuthentication} /> ); } }
  15. const withUser = BaseComponent => class extends React.Component { static

    contextTypes = { user: UserShape }; render() { return ( <BaseComponent {...this.props} user={this.context.user} /> ); } };
  16. Helps with passing data down the component tree Introduces indirectness

    Not needed when there is a global state manager Abstract how data is passing
  17. const Item = ({ children, isHighlighted }) => ( <section

    className={isHighlighted ? 'highlighted' : ''}> {children} </section> )
  18. class Carousel extends Component { state = { activeIndex: null

    }; render() { const { state: { activeIndex }, props: { children } } = this; return ( <div> <PrevButton onClick={this.handlePreviewClick()} /> {this.renderActiveItem(children, activeIndex)} {this.renderDottedMenu(children)} <NextButton onClick={this.handleNextClick()} /> </div> ); }
  19. ... renderActiveItem(items, activeIndex) { const item = React.Children.toArray(items).find((item, index) =>

    { if (activeIndex !== null) return index === activeIndex; return item.props.active === true; }); return React.cloneElement(item, { isHighlighted: true }); } ...
  20. Enables creating general purpose sharable components Require a lot of

    imperative manual work Local state harder to manage from outside
  21. Component with State Machine, inspired by ReasonReact, feels like Redux

    in component Rediscovered from recompose (bet all familiar with redux had this idea)
  22. class Counter extends ReducerComponent { state = { count: 0

    }; increment(_e) { return "increment"; } decrement(_e) { return "decrement"; }
  23. reducer(state, action) { switch (action) { case "increment": return {

    count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; } }
  24. class ReducerComponent extends React.Component { reducer(state, action) { return state;

    } dispatch(actionCreator) { return (...eventArgs) => { this.setState((prevState) => { this.reducer(prevState, actionCreator(...eventArgs)); }); }; } }
  25. Simple to manage a complex state For small apps/complex local

    states Requires familiarity with a reducing state Not needed when other State libraries are present (Redux/MobX)
  26. Higher Order Component Lets you add behaviour to components Function

    As a Child (Render Function) Lets you focus on what will be rendered Provider Component Lets you pass data deep down the component tree Compound Components Lets you create groups of components with shared state