Using Recompose To Share Functionality Between React Components

A comprehensive guide to Recompose's utility belt for function components and higher-order components, including migration to React Hooks

What Is Recompose?

Sharing functionality between React components is a fundamental need in building maintainable applications. The concept involves establishing behavior in one place and extending it across different components. Higher-Order Components (HOCs) have been one traditional approach to achieving this, and Recompose emerged as a powerful utility library that made working with HOCs significantly easier.

Recompose was created by Andrew Clark, a core contributor to React, as a utility belt for function components and higher-order components. The library was described as "lodash for React" - a collection of composable utilities that could enhance functional components with various capabilities. The philosophy behind Recompose was to treat component logic as small, focused functions that could be composed together to create complex behavior.

This functional programming approach stood in contrast to class-based components and offered several advantages. Each HOC does one thing well, making code easier to understand and test. Common patterns like state management, lifecycle methods, and context could be extracted and reused across components. Multiple HOCs could be combined using the compose function, enabling a pipeline of transformations. Since Recompose consisted of small, independent functions, bundlers could eliminate unused code through tree-shaking.

The library gained popularity because it addressed a real gap in the React ecosystem - functional components were limited in capability compared to class components, and Recompose bridged that gap without requiring developers to convert to class-based components. While React Hooks have largely superseded Recompose, understanding this library provides valuable insights into component composition patterns that continue to influence modern React development.

Our web development team applies these composition patterns daily when building maintainable React applications for clients across various industries.

Core Recompose HOCs

Essential utilities for enhancing functional components

withState

Add local state management to functional components

withHandlers

Create event handlers that access props and state

compose

Combine multiple HOCs into a single enhanced component

withReducer

Implement Redux-like state management patterns

Adding State with withState

The withState HOC was one of Recompose's most commonly used utilities, enabling functional components to manage local state without converting to class components. Before React Hooks, this was a significant innovation that allowed developers to keep the benefits of functional components while adding stateful behavior.

The withState HOC follows this signature:

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

The withState HOC could also derive initial state from props, enabling components to initialize state based on their configuration. This flexibility made withState suitable for a wide range of use cases, from simple toggle buttons to more complex form components.

State updater functions can receive either a direct value or a function that receives the previous state and returns the new state, following the same pattern as React's useState hook. This approach eliminates the need for class components when managing simple local state in functional components.

withState Example
1import { withState } from 'recompose';2 3const Counter = withState('count', 'setCount', 0)(4 ({ count, setCount }) => (5 <div>6 <p>Count: {count}</p>7 <button onClick={() => setCount(n => n + 1)}>8 Increment9 </button>10 </div>11 )12);
withHandlers Example
1import { withHandlers, withState } from 'recompose';2 3const enhance = compose(4 withState('value', 'setValue', ''),5 withHandlers({6 handleChange: props => event => {7 props.setValue(event.target.value);8 },9 handleSubmit: props => event => {10 event.preventDefault();11 console.log('Submitted:', props.value);12 }13 })14);

Managing Handlers with withHandlers

The withHandlers HOC allowed components to define event handlers as functions that could access props and state. It addressed the challenge of creating handlers that needed to interact with component props or perform complex operations.

withHandlers takes an object mapping handler names to factory functions. Each factory function receives props and returns an event handler. The key insight is that handlers are created as functions that close over the current props. This means handlers always have access to the latest props, even as they change over time.

The factory function pattern (returning a function from a function) enabled handlers to access props at the time they are invoked. This approach solved the common problem of stale closures in event handlers. Handlers defined with withHandlers could be used for form input handling, API calls triggered by user interaction, complex state transitions, and integration with external libraries.

This pattern is now commonly replaced by React's useCallback hook, which provides similar referential stability for callback functions.

Composing HOCs with compose

The compose function was essential to Recompose's compositional philosophy. It allowed multiple HOCs to be combined into a single enhanced component, creating a pipeline of transformations that could be applied to any component.

The compose function applies HOCs from right to left (bottom to top), which might seem counterintuitive but follows functional programming conventions. This means withHandlers is applied first (innermost), followed by withState (outermost). The base component receives props from both HOCs, handlers have access to the state and setter from withState, and the final enhanced component provides all functionality as props.

Why Use compose?

  • Readability: The HOC chain is clearly visible in one place
  • Maintainability: Adding or removing HOCs is straightforward
  • Debugging: The composition order is explicit
  • Reusability: The enhanced component can be easily modified

Modern React development achieves similar composability through custom hooks, which can combine multiple Hooks and be composed more naturally without the wrapper component nesting that characterized HOC-based approaches.

compose Function Example
1import { compose, withState, withHandlers } from 'recompose';2 3const enhance = compose(4 withState('count', 'setCount', 0),5 withHandlers({6 increment: props => () => props.setCount(n => n + 1),7 decrement: props => () => props.setCount(n => n - 1)8 })9);10 11const Counter = enhance(({ count, increment, decrement }) => (12 <div>13 <button onClick={decrement}>-</button>14 <span>{count}</span>15 <button onClick={increment}>+</button>16 </div>17));

Redux-Like State with withReducer

For components requiring more complex state logic similar to Redux reducers, Recompose provided withReducer. This HOC enabled pattern-based state updates familiar to developers who had used Redux or similar state management libraries.

The withReducer HOC followed this signature:

withReducer(
 stateName: string, // the name of the state
 dispatchName: string, // the dispatch method name
 reducer: (state, action) => newState, // reducer function
 initialState: S | (props) => S // initial state
): HigherOrderComponent

The withReducer HOC was particularly useful for complex state transitions with multiple action types, state logic that needed to be tested in isolation, components that would benefit from Redux-like predictability, and migrating Redux logic into component-local state.

When to Use withReducer

  • Complex state transitions with multiple action types
  • State logic that needed to be tested in isolation
  • Components benefiting from Redux-like predictability
  • Migrating Redux logic into component-local state

The action-based model made state changes explicit and traceable, improving code maintainability for complex components. This pattern directly maps to React's useReducer hook, which provides the same action-based state update pattern.

withReducer Example
1const enhance = compose(2 withReducer(3 'count',4 'dispatch',5 (state, action) => {6 switch (action.type) {7 case 'INCREMENT':8 return state + 1;9 case 'DECREMENT':10 return state - 1;11 default:12 return state;13 }14 },15 016 ),17 withHandlers({18 increment: ({ dispatch }) => () =>19 dispatch({ type: 'INCREMENT' })20 })21);

Migration From Recompose To React Hooks

The introduction of React Hooks in version 16.8 fundamentally changed how developers shared functionality between components. Hooks provided a more intuitive, less verbose, and more powerful approach than Recompose's HOC-based model.

Key advantages of Hooks over Recompose included: No Wrapper Hell - Hooks are called directly inside components, eliminating the nested wrapper problem that occurred with multiple HOCs. Better TypeScript Support - Hooks have excellent TypeScript inference, while HOC type declarations were complex. Easier Testing - Hooks can be tested by calling them directly in tests. More Flexible Logic Sharing - Custom hooks can combine multiple Hooks and can be composed more naturally. No Props Drilling - Context-based hooks like useContext work seamlessly without prop manipulation. Active Maintenance - React Hooks are a core React feature with ongoing improvements.

For teams using Recompose, migration became necessary because no new features would be added to Recompose, bug fixes would eventually stop, new developers would be unfamiliar with Recompose patterns, and the React ecosystem had standardized around Hooks. Our React development specialists can help you navigate this migration smoothly.

Recompose HOC to React Hook Translations
Recompose HOCReact HookNotes
withStateuseStateDirect equivalent, similar signature
withHandlersuseCallbackReference equality matters for callbacks
withStateHandlersuseState + useCallbackCombine state and handlers
withReduceruseReducerRedux-like pattern
withContextuseContextDirect replacement
lifecycle methodsuseEffectcomponentDidMount, DidUpdate, WillUnmount

Performance Considerations

Both Recompose and Hooks have performance characteristics that developers should understand. While modern React applications rarely need to optimize component rendering preemptively, understanding these patterns helps when performance issues arise.

Recompose Performance Concerns

  • Wrapper Component Depth: Multiple HOCs created deeply nested component trees, which could impact both memory usage and rendering performance
  • Props Creation: Each HOC that added props created new object references, potentially causing unnecessary re-renders in child components
  • Function Memoization: Without careful attention, handlers created by withHandlers could be recreated on every render

Hooks Performance Concerns

  • Dependencies in Hooks: useEffect, useCallback, and useMemo require correct dependency arrays, and incorrect dependencies can cause stale closures or unnecessary re-runs
  • State Updates: Multiple state variables updated independently can cause separate re-renders (though useReducer can consolidate related state)
  • Custom Hook Overhead: While minimal, custom hooks do add some function call overhead compared to inline code

Memoization Strategies

With React Hooks, React.memo provides component-level memoization. The comparison function receives previous and next props, returning true if props are equal (skip re-render) or false to re-render:

const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => {
 return prevProps.data === nextProps.data;
});

For handlers, useCallback prevents creating new function references on each render:

const handleClick = useCallback(() => {
 doSomething(props.id);
}, [props.id]);

The key principle across both approaches is to only memoize when necessary. Premature optimization can actually harm performance by adding unnecessary overhead and making code harder to maintain.

For developers exploring modern approaches to CSS-in-JS and component styling, understanding performance implications of different styling approaches is equally important for building optimized applications.

Modern React Best Practices

Patterns for sharing functionality in contemporary React development

Custom Hooks

Extract shared logic into reusable custom hooks

React Context

Use Context for global state accessible throughout the tree

useReducer

Consolidate complex state logic with action-based updates

useCallback

Memoize handlers to prevent unnecessary re-renders

Conclusion

Recompose represented an important step in the evolution of React component patterns. By providing a collection of composable HOCs, it enabled developers to enhance functional components with capabilities that previously required class components. The library's emphasis on functional composition influenced how developers thought about component architecture.

However, React Hooks have largely subsumed Recompose's functionality while offering significant advantages in terms of code organization, TypeScript support, and developer experience. For new projects, Hooks should be the default choice. For existing Recompose codebases, migration is achievable through systematic application of HOC-to-Hook translations.

The principles that guided Recompose - modularity, composability, and reusability - remain relevant in Hooks-based development. Custom Hooks embody these same principles while integrating more naturally with React's rendering model. Understanding Recompose provides a foundation for appreciating both how React evolved and why Hooks represent a superior approach for modern React development.

If you're working with legacy React codebases that use Recompose, our experienced development team can help you modernize your application architecture and migrate to contemporary React patterns.

Frequently Asked Questions

Ready to Modernize Your React Development?

Our team of React experts can help you migrate from legacy patterns to modern Hooks-based development.