The case for recompose

recompose gives you a bunch of utilities as higher order components to functionally compose your React components to do all kinds of things declaratively. If that makes sense, think of it like a wardrobe for your React components - giving them different capabilities based on the clothes (decorations) they put on.

I’m a strong proponent of functional programming concepts in javascript - and if you are too, let me make the case for recompose.

Eliminating State

Sometimes, in react components, you tend to have state - which reduces the testability of the component, makes it bulkier (by having instance methods to setState), and makes us write more code. Recompose helps you rid your whole codebase of the pesky state. (Well, rid you of having to deal with it anyway)

Consider the widely-used withState util.

const withButtonState = withState("isActive", "setIsActive", false);

const MyComponent = withButtonState((props) => (
  <Button active={props.isActive} onClick={() => props.setIsActive(true)} />
));

See how your component does not have to have instance methods and be stateful? withState magically provides you with a setIsActive method in props which can change a isActive prop. Since withState and other higher order components (HOCs) are composable, you can get a bunch of these together and compose them to have a more complex state as well.

const withButtonState = withState("isActive", "setIsActive", false);
const withMessageState = withState("message", "setMessage", "This is my default message");

const withMyComponentState = compose(withButtonState, withMessageState); // recompose comes with compose too

const MyComponent = withMyComponentState((props) => {
  ...
});

Code reusability

Recompose is also very handy for decorating you React component with re-usable domain specific code.

Assume you have a instance method in a React component called handleSubmit which handles validations, error handling for a form in your application.

class MyFormComponent extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault();

    try {
      validateData(this.props.data);
    } catch (e) {
      raiseValidationError(e);
      return;
    }

    submitData(this.props.data);

    // do other things
  }

  render() {
    // ...
  }
}

Assuming you want to reuse this handleSubmit functionality elsewhere in the application - using withHandlers, you can push this function out into a “handler”. That will be available to your component as a prop.

const withOnSubmitHandler = withHandlers({
  onSubmit: props => event => {
    // .. do the same as above handleSubmit here
  }
});

Now since you have this as a prop - come testing time, you just pass a mock function to onSubmit without having to mock an instance method. And since you don’t rely on a function outside of the React component/stateless function, your UI representation code becomes a pure function!

Short-hand lifecycle methods

To make use of lifecycle methods, you have to instantiate class-based React components instead of stateless components. Luckily, recompose gives you a plethora of shorthands to help you not make a class-based React component again.

For an example, consider the often used pure-rendering optimation with shouldComponentUpdate.

  ...

  shouldComponentUpdate(nextProps) {
    const propsChanged = nextProps !== this.props;

    if (propsChanged) {
      return true;
    }

    return false;
  }

  ...

To do the same thing, you can jsut decorate your component with pure(). This will encapsulate shouldComponentUpdate logic, so you don’t have to repeat that for your React components.

Need to do shouldComponentUpdate optimation based on a subset of your props? Just use onlyUpdateForKeys(). Or have another lifecycle method that you want to tap into? Just use lifecycle() HOC.

Utility

Have a composed component heirarchy and you need to hoist static methods? Use hoistStatics(). This is really useful when you have child components specifying static properties and methods that and ancestor must be aware of. Like specifying graphql fragments in children, and combining those fragments from a ancestral container. (Static methods by default don’t get passed up though the composed hierarchy and is available to the parent component only.)

Dealing with context? withContext() and getContext() to the rescue. Keep your components stateless - while doing everything you need to do with context.

Want functional syntactic sugar to set a display name? setDisplayName() lets you do that too.

I’m not going to go into much detail here - because the API documentation does a great job. Happy recomposing!

Written on June 7, 2018