Skip to content
This repository has been archived by the owner on Sep 10, 2022. It is now read-only.

Recipes

Tim Kindberg edited this page Jun 1, 2017 · 17 revisions

Write a Higher Order Component from Scratch

It's good to understand how to write your own HOC without recompose so you understand how Recompose can help reduce boilerplate. This example does not use Recompose to create two HOCs. See it in Plunkr

const { Component } = React;

const overrideProps = (overrideProps) => (BaseComponent) => (props) =>
  <BaseComponent {...props} {...overrideProps} />;

const alwaysBob = overrideProps({ name: 'Bob' });

const neverRender = (BaseComponent) =>
  class extends Component {
    shouldComponentUpdate() {
      return false;
    }
    render() {
      return <BaseComponent {...this.props} />;
    }
  };

const User = ({ name }) =>
  <div className="User">{ name }</div>;

const User2 = alwaysBob(User);
const User3 = neverRender(User);

const App = () =>
  <div>
    <User name="Tim" />
    <User2 name="Joe" />
    <User3 name="Steve" />
  </div>;

by @kindberg

Mix HOC from Various Libraries

Use compose to mix and match HOC from recompose and other libraries such as redux' connect HOC. See it in Plunkr

const { Component } = React;
const { compose, setDisplayName, setPropTypes } = Recompose;
const { connect } = Redux();

const enhance = compose(
  setDisplayName('User'),
  setPropTypes({
    name: React.PropTypes.string.isRequired,
    status: React.PropTypes.string
  }),
  connect()
);

const User = enhance(({ name, status, dispatch }) =>
  <div className="User" onClick={
      () => dispatch({ type: "USER_SELECTED" })
    }>
    { name }: { status }
  </div>
);

by @kindberg

Reusable withToggle HOC

Can help with interactions that require a simple show/hide such as tooltips, dropdowns, expandable menus, etc. See it in Plunkr

const { Component } = React;
const { compose, withState, withHandlers } = Recompose;

const withToggle = compose(
  withState('toggledOn', 'toggle', false),
  withHandlers({
    show: ({ toggle }) => (e) => toggle(true),
    hide: ({ toggle }) => (e) => toggle(false),
    toggle: ({ toggle }) => (e) => toggle((current) => !current)
  })
)

const StatusList = () =>
  <div className="StatusList">
    <div>pending</div>
    <div>inactive</div>
    <div>active</div>
  </div>;

const Status = withToggle(({ status, toggledOn, toggle }) =>
  <span onClick={ toggle }>
    { status }
    { toggledOn && <StatusList /> }
  </span>
);

const Tooltip = withToggle(({ text, children, toggledOn, show, hide }) =>
  <span>
    { toggledOn && <div className="Tooltip">{ text }</div> }
    <span onMouseEnter={ show } onMouseLeave={ hide }>{ children }</span>
  </span>
);

const User = ({ name, status }) =>
  <div className="User">
    <Tooltip text="Cool Dude!">{ name }</Tooltip><Status status={ status } />
  </div>;

const App = () =>
  <div>
    <User name="Tim" status="active" />
  </div>;

by @kindberg

And again but using withReducer helper See it in Plunkr

const { Component } = React;
const { compose, withReducer, withHandlers } = Recompose;

const withToggle = compose(
  withReducer('toggledOn', 'dispatch', (state, action) => {
    switch(action.type) {
      case 'SHOW':
        return true;
      case 'HIDE':
        return false;
      case 'TOGGLE':
        return !state;
      default:
        return state;
    }
  }, false),
  withHandlers({
    show: ({ dispatch }) => (e) => dispatch({ type: 'SHOW' }),
    hide: ({ dispatch }) => (e) => dispatch({ type: 'HIDE' }),
    toggle: ({ dispatch }) => (e) => dispatch({ type: 'TOGGLE' })
  })
);

// Everything else is the same...

by @kindberg

Perform a Tranformation on a Prop Before the Component Receives it

In this example we take a flexible and generic UserList component and we populate it with specific users three ways to form three specific components that will filter out only the users that the component is meant to show. See it in Plunkr

const { Component } = React;
const { mapProps } = Recompose;

const User = ({ name, status }) =>
  <div className="User">{ name }{ status }</div>;

const UserList = ({ users, status }) =>
  <div className="UserList">
    <h3>{ status } users</h3>
    { users && users.map((user) => <User {...user} />) }
  </div>;

const users = [
  { name: "Tim", status: 'active' },
  { name: "Bob", status: 'active' },
  { name: "Joe", status: 'active' },
  { name: "Jim", status: 'inactive' },
];

const filterByStatus = (status) => mapProps(
  ({ users }) => ({
    status,
    users: users.filter(u => u.status === status)
  })
);

const ActiveUsers = filterByStatus('active')(UserList);
const InactiveUsers = filterByStatus('inactive')(UserList);
const PendingUsers = filterByStatus('pending')(UserList);

const App = () =>
  <div className="App">
    <ActiveUsers users={ users } />
    <InactiveUsers users={ users } />
    <PendingUsers users={ users } />
  </div>;

by @kindberg

Create Specific Components Based on Generic Components

It can be helpful to save a certain configuration of props into a pre-configured component. See it in Plunkr

const { Component } = React;
const { withProps } = Recompose;

const HomeLink = withProps(({ query }) => ({ href: '#/?query=' + query }))('a');
const ProductsLink = withProps({ href: '#/products' })('a');
const CheckoutLink = withProps({ href: '#/checkout' })('a');

const App = () =>
  <div className="App">
    <header>
      <HomeLink query="logo">Logo</HomeLink>
    </header>
    <nav>
      <HomeLink>Home</HomeLink>
      <ProductsLink>Products</ProductsLink>
      <CheckoutLink>Checkout</CheckoutLink>
    </nav>
  </div>;

by @kindberg

Show a Spinner While a Component is Loading

Pretty self explanatory :) See it in Plunkr

const { Component } = React;
const { compose, lifecycle, branch, renderComponent } = Recompose;

const withUserData = lifecycle({
  state: { loading: true },
  componentDidMount() {
    fetchData().then((data) =>
      this.setState({ loading: false, ...data }));
  }
});

const Spinner = () =>
  <div className="Spinner">
    <div className="loader">Loading...</div>
  </div>;

const isLoading = ({ loading }) => loading;

const withSpinnerWhileLoading = branch(
  isLoading,
  renderComponent(Spinner)
);

const enhance = compose(
  withUserData,
  withSpinnerWhileLoading
);

const User = enhance(({ name, status }) =>
  <div className="User">{ name }{ status }</div>
);

const App = () =>
  <div>
    <User />
  </div>;

by @kindberg

Show Error Messages based on Non-Optimal States

The idea here is that your components could contain only the "happy path" rendering logic. All other unhappy states—loading, insufficient data, no results, errors—could be handled via a nonOptimalStates HOC. See it in Plunkr

const { Component } = React;
const { compose, lifecycle, branch, renderComponent } = Recompose;

const User = ({ name, status }) =>
  <div className="User">{ name }{ status }</div>;

const withUserData = lifecycle({
  componentDidMount() {
    fetchData().then(
      (users) => this.setState({ users }),
      (error) => this.setState({ error })
    );
  }
});

const UNAUTHENTICATED = 401;
const UNAUTHORIZED = 403;
const errorMsgs = {
  [UNAUTHENTICATED]: 'Not Authenticated!',
  [UNAUTHORIZED]: 'Not Authorized!',
};

const AuthError = ({ error }) =>
  error.statusCode &&
    <div className="Error">{ errorMsgs[error.statusCode] }</div>;

const NoUsersMessage = () =>
  <div>There are no users to display</div>;

const hasErrorCode = ({ error }) => error && error.statusCode;
const hasNoUsers = ({ users }) => users && users.length === 0;

const nonOptimalStates = (states) =>
  compose(...states.map(state =>
    branch(state.when, renderComponent(state.render))));

const enhance = compose(
  withUserData,
  nonOptimalStates([
    { when: hasErrorCode, render: AuthError },
    { when: hasNoUsers, render: NoUsersMessage }
  ])
);

const UserList = enhance(({ users, error }) =>
  <div className="UserList">
    { users && users.map((user) => <User {...user} />) }
  </div>
);

const App = () =>
  <div className="App">
    <UserList />
  </div>;

by @kindberg

For more great Recompose learning watch @timkindberg's egghead.io course.

Clone this wiki locally