Tuesday, December 10

Using Recompose to Share Functionality Between React Components

Sharing functionality between React components is a pretty common need. The concept is that we can establish the behavior in one place and then extend it across different components. Higher-Order Components are one way to do this. Yet, there is another way using a library called Recompose.

GitHub Repo

What is Recompose?

The documentation helps us answer that:

Recompose is a React utility belt for function components and higher-order components. Think of it like lodash for React.

Basically, it’s a library for React that contains a bunch of helpers that return different higher-order components — which is nice because it takes some of the grunt work out of defining common React patterns and making them immediately available to extend to other components.

What exactly can it do? Well, let’s walk through a few examples together.

Add state to functional stateless components

If you couldn’t guess it by the name, a functional stateless component does not have any states. It merely accepts props and returns UI to the front end.

const Greeting = props =>
  <p>
    Hello, {props.name}!
  </p>

In scenarios where you want to make use of state in your functional stateless component, you have to convert it to a class component. This is where Recompose comes in handy.

Recompose provides you with the withState() helper to add state to your functional stateless components. It manages a single state value. You cannot manage more than one state value in withState() like you will do in your class component. You pass in a function to update the state value and an optional default stated value.

Here’s how withState() is structured:

withState(
  stateName: string, // the name we call our state
  stateUpdaterName: string, // the name of the function to call
  initialState: any | (props: Object) => any // optional default state to pass
): HigherOrderComponent

A counter is a common example that’s used to demonstrate a concept. Here’s how we can create a super simple counter using Recompose’s withState() helper:

const App = withState("count", "handleCounter", 0)(({ count, handleCounter }) => {
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => handleCounter(n => n + 1)}>Increment</button>
      <button onClick={() => handleCounter(n => n - 1)}>Decrement</button>
    </div>
  );
});

Since the withState() helper is already available to us, we can call it right away and provide it with the parameters it needs. Again, those are:

  • stateName: The name we call our state
  • stateUpdaterName: The name of the function to call
  • initialState: Optional default state to pass

Those parameters are then integrated into the UI markup we want to render on the front end.

There’s another way we could have made our counter component and it’s worth looking at to get more practice putting a Recompose helper to use.

First, we create a higher-order component using withState() and the required parameters.

const enhanced = withState("counter", "handleCounter", 0);

Next, we make the Counter component, working in the withState() parameters:

const Counter = ({ counter, handleCounter }) => (
  <div>
    <h1>{counter}</h1>
    <button onClick={() => handleCounter(n => n + 1)}>Increment</button>
    <button onClick={() => handleCounter(n => n - 1)}>Decrement</button>
  </div>
);

Note that the name of the state and updater function is passed as props to the Counter component.

Finally, we create our App component by wrapping the Counter component in the higher-order enhanced component.

const App = enhanced(Counter);

See the Pen Recompose withState by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Here is another popular approach:

const enhanced = withState("count", "handleCounter", 0);
const App = enhanced(({ count, handleCounter }) => {
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => handleCounter(n => n + 1)}>Increment</button>
      <button onClick={() => handleCounter(n => n - 1)}>Decrement</button>
    </div>
  );
});

See the Pen Recompose withState v2 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Handle state using withHandlers()

Recompose also has a withHandlers() helper that allows you handle state by defining functions that will be used to update a component’s state. And, you can use it right alongside withState()!

These are basically higher-order functions that take in props and return a function handler. Let’s break down the structure like we did in the previous example.

withHandlers({
  incrementCounter: props => event => {
    event.preventDefault();
    props.handleCounter(props.count + 1);
  }
})

First off, we’ve identified incrementCounter, which will be available to our Counter component to update the count value on click.

Next, we construct the counter like we did before — as a higher-order component using withState():

const enhancedState = withState("count", "handleCounter", 0);

Now, we define our functions putting withHandlers() to use:

const enhancedHandler = withHandlers({
  incrementCounter: props => event => {
    event.preventDefault();
    props.handleCounter(props.count + 1);
  },
  decrementCounter: props => event => {
    event.preventDefault();
    props.handleCounter(props.count - 1);
  }
});

We’ve constructed a higher-order component we’re calling enhancedHandler and used withState() to define two function handlers in it: incrementCounter and decrementCounter. These handlers contain parameters that will be received as props by the components that call them. They will be needed if we want to update the state of the component defined using withState().

Alright, on to creating our counter component:

const Counter = ({ count, incrementCounter, decrementCounter }) => (
  <div>
    <h1>{count}</h1>
    <button onClick={incrementCounter}>Increment</button>
    <button onClick={decrementCounter}>Decrement</button>
  </div>
);

See that? The state and handlers are expected to be passed as props to the counter component.

To make use of the higher-order components we defined, we have to pass the Counter component to enhancedHandler and wrap that as a parameter to enhancedState.

In other words:

const App = enhancedState(enhancedHandler(Counter));

See the Pen Recompose withState & withHandlers by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Composing multiple higher-order components

In that last example, we made use of two higher-order components. Is there a better of chaining them together? Most definitely! Recompose provides us with a compose() helper to do exactly that. We can use compose() to create a component that composes both higher-order components in one fell swoop.

const enhanced = compose(
  withState("count", "handleCounter", 0),
  withHandlers({
    incrementCounter: props => event => {
      event.preventDefault();
      props.handleCounter(props.count + 1);
    },
    decrementCounter: props => event => {
      event.preventDefault();
      props.handleCounter(props.count - 1);
    }
  })
);

Now, we can use the enhanced component in our App component:

const App = enhanced(({ count, incrementCounter, decrementCounter }) => {
  return (
    <div>
      <p>{count}</p>
      <button onClick={incrementCounter}>Increment</button>
      <button onClick={decrementCounter}>Decrement</button>
    </div>
  );
});

See the Pen Recompose – compose withState & withHandlers by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Manage state using Redux like reducer

Another nice thing Recompose does is allow you to manage state using a reducer function (withReducer). A reducer updates the state of a component in response to a particular action.

The structure of the withReducer() looks like this:

withReducer<S, A>(
  stateName: string,
  dispatchName: string,
  reducer: (state: S, action: A) => S,
  initialState: S | (ownerProps: Object) => S
): HigherOrderComponent</S>

The first parameter is the name of the state. The second is the dispatch method. dispatch will be used in dispatching actions like we have in Redux. Next, we have the reducer and the initial state.

In the context of our counter component, withReducer() will update the state of the count: the count will go up with an action we’ll call increment and, conversely, the count will go down with an action we’ll call decrement.

First, we create an enhanced component by composing withReducer and withHandlers.

const enhanced = compose(
  withReducer(
    "count",
    "dispatch",
    (state, action) => {
      switch (action.type) {
        case "INCREMENT":
          return state + 1;
        case "DECREMENT":
          return state - 1;
        default:
          return state;
      }
    },
    0
  ),
  withHandlers({
    incrementCounter: ({ dispatch }) => e => dispatch({ type: "INCREMENT" }),
    decrementCounter: ({ dispatch }) => e => dispatch({ type: "DECREMENT" })
  })
);

incrementCounter and decrementCounter will respond to DOM events and dispatch an action type. The state will be updated depending on the action type. Next, we need to make use of this in our component.

const App = enhanced(({ count, incrementCounter, decrementCounter }) => {
  return (
    <div>
      <p>{count}</p>
      <button onClick={incrementCounter}>Increment</button>
      <button onClick={decrementCounter}>Decrement</button>
    </div>
  );
});

See the Pen Recompose – reducer by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Pretty neat, right?

Hopefully this gives you a good idea of what Recompose is and how the library’s wealth of helpers can streamline React development, particularly when it comes to managing and calling states.

Naturally, there’s a lot more to Recompose than what we’ve covered here. A quick scan of the API documentation will show that there are many higher-order components that you can put to use, depending on your application’s requirements. Go forth, build, and please feel free to drop a comment in if you have any questions!

The post Using Recompose to Share Functionality Between React Components appeared first on CSS-Tricks.


Source: CSS-tricks.com

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x