recompose
recompose is a JavaScript utility library for React, providing a collection of higher-order components (HOCs) and utility functions designed to enhance functional component patterns. It allows developers to compose multiple concerns into a single component, such as managing local state (`withState`), handling side effects (`lifecycle`), mapping props (`mapProps`), and creating event handlers (`withHandlers`). The current stable version is 0.30.0, released in late 2018. The author explicitly announced the discontinuation of active maintenance in October 2018, recommending React Hooks as a superior alternative that addresses the same problems and more. Its primary differentiator was enabling a purely functional approach to component logic before the native React Hooks API existed, offering a declarative way to abstract component logic and state management without relying on class components.
Common errors
-
TypeError: (0 , _recompose.compose) is not a function
cause Attempting to use `compose` with a CommonJS `require` call where the `recompose` package is designed for ES module imports or a specific CJS export structure is not being respected.fixEnsure you are using `import { compose } from 'recompose';` in an ES module context or configure your bundler (e.g., Webpack, Rollup) to correctly handle interop between CommonJS and ES modules. If strictly in a CommonJS environment, verify the exact export structure or use `const compose = require('recompose').compose;`. -
Invariant Violation: You supplied a function as the second argument to withState, but it must be an object.
cause Incorrect usage of `withStateHandlers`. This HOC expects an object as its second argument, where keys are handler names and values are functions that return a new state object. It is often confused with `withState` which takes a function for the initial state and a string for the setter name.fixRefactor `withStateHandlers` calls. The second argument should be an object mapping updater names to functions that receive the current state and props, and return a *partial* state object. Example: `withStateHandlers({ value: '' }, { updateValue: () => (event) => ({ value: event.target.value }) })`. -
Warning: componentWillMount has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-lifecycles for details.
cause This warning occurs because the `lifecycle` HOC in `recompose` uses deprecated (and now prefixed `UNSAFE_`) React lifecycle methods like `componentWillMount`.fixAvoid using `lifecycle` for new functionality. For existing code, consider refactoring the logic into React Hooks (`useEffect`) or standard React class component `componentDidMount` if a full migration isn't feasible immediately. React discourages the use of `UNSAFE_` methods. -
Property 'someProp' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<InferableComponentEnhancerWithProps<any, any>, any, any>> & Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps>'.cause This TypeScript error indicates a mismatch in prop types when composing HOCs with `recompose`. TypeScript struggles to correctly infer the final props passed to the base component, especially with complex HOC chains, often leading to 'any' or incorrect type unions.fixExplicitly define and pass prop types at each stage of the HOC composition using generics in `compose` or by defining the `ComponentEnhancer` types. Ensure the final component's props interface correctly extends all injected props from the HOCs. Consult `@types/recompose` definitions for correct `ComponentEnhancer` or `InferableComponentEnhancerWithProps` usage.
Warnings
- breaking The `withStateHandler` HOC's behavior changed in v0.30.0, making state changes more similar to `React.setState`. This might affect how state updates are handled, especially with synchronous vs. asynchronous updates or the merging of partial state objects. Review existing usages to ensure compatibility.
- breaking In v0.26.0, `recompose` removed 'eager optimizations' (where `createElement` was sometimes replaced with direct function calls). This change was a response to various issues and could alter rendering behavior, potentially impacting performance or causing unexpected side effects in certain scenarios.
- gotcha Version 0.25.0 introduced 'production only optimizations' which meant certain eager factory optimizations were only applied in production environments. This could lead to behavioral differences or unexpected issues during development that were not present in production builds.
- deprecated `recompose` is effectively abandoned by its author in favor of React Hooks (introduced in React 16.8). While existing code will continue to work, active development, new features, and compatibility fixes for future React versions are not expected.
- deprecated The `lifecycle` HOC internally uses deprecated React lifecycle methods (`componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate`). Using these methods, even indirectly through `recompose`, will trigger `UNSAFE_` warnings in modern React versions (16.3+), indicating they may cause issues with upcoming features like Concurrent Mode.
- deprecated `recompose` uses `React.createFactory()` internally, which was deprecated in React 16.13.1 and will be removed in a future major release. This will cause warnings and eventually breakage with newer React versions.
Install
-
npm install recompose -
yarn add recompose -
pnpm add recompose
Imports
- compose
const compose = require('recompose').compose;import { compose } from 'recompose'; - withState
import withState from 'recompose/withState';
import { withState } from 'recompose'; - withHandlers
const { withHandlers } = require('recompose');import { withHandlers } from 'recompose';
Quickstart
import React from 'react';
import { compose, withState, withHandlers, lifecycle } from 'recompose';
interface CounterProps {
count: number;
increment: () => void;
decrement: () => void;
message: string;
}
const CounterDisplay: React.FC<CounterProps> = ({ count, increment, decrement, message }) => (
<div>
<h1>{message}</h1>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
const enhance = compose<
CounterProps,
{ initialCount?: number; initialMessage?: string }
>(
withState('count', 'setCount', ({ initialCount = 0 }) => initialCount),
withState('message', 'setMessage', ({ initialMessage = 'Hello Recompose!' }) => initialMessage),
withHandlers({
increment: ({ setCount }) => () => setCount((prevCount: number) => prevCount + 1),
decrement: ({ setCount }) => () => setCount((prevCount: number) => prevCount - 1)
}),
lifecycle({
componentDidMount() {
console.log('Counter component mounted.');
this.props.setMessage('Welcome to the Recompose Counter!');
},
componentDidUpdate(prevProps: CounterProps) {
if (this.props.count !== prevProps.count) {
console.log(`Count changed from ${prevProps.count} to ${this.props.count}`);
}
}
})
);
const EnhancedCounter = enhance(CounterDisplay);
// Example usage in an application (e.g., App.tsx)
// function App() {
// return (
// <div style={{ padding: '20px' }}>
// <EnhancedCounter initialCount={5} />
// </div>
// );
// }
// export default App;