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.
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 ( {count}
);
});
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 statestateUpdaterName
: The name of the function to callinitialState
: 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 }) => ( {counter}
);
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 ( {count}
);
});
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 }) => ( {count}
);
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 ( {count}
);
});
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 ( {count}
);
});
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.