How-to: A gradual migration to React Hooks
…from recompose.
The React Hooks 16.8.0 version has been released on February 06, 2019. The whole community are excited about it. It introduces no breaking changes (thanks to the awesome React team!), thus you can gradually use it within your existing codebase. That leads to an interesting question:
If my codebase are full of recompose stacks,
how do I gradually migrate them into React Hooks (or custom hooks)?
A recompose stack is something like this:
import * as R from "recompose";
const enhance = R.compose(
R.withProps({text: "Click Me"}),
R.withStateHandler({ active: false }, {
onClick: ({ active }) => event => ({
active: !active,
}),
}),
connect(mapStateToProps, mapDispatchToProps) // react-redux
);
With Built-in Hooks
As you may imagine, some recompose HOCs are trivial being replaced by built-in React Hooks. For example,
- withState() →
useState
- withHandlers() →
useCallback
- withStateHandlers() →
useState + useCallback
- withReducer() →
useReducer
- withContext() ⇢
useContext
However, some aren’t:
- mapProps()
- withProps()
- branch()
- …and so many you could name it.
To understand whether we could upgrade a HOC to be implemented with React Hooks, we need to understand… what is a HOC?
What is a HOC?
A HOC, by definition, is a Higher-Order-Components. It is a function that returns a React component when it was called with another React component.
Let’s say we have a HOC, say renderOnlyOnMonday, what it does is, it will only render the another React component when the current date is on Monday.
export const renderOnlyOnMonday = BaseComponent => {
return props => {
const isMonday = dateFns.getDay(new Date()) === 1;
if (isMonday) { return <BaseComponent {...props} />; }
return <noscript />;
}
}
// Use it like this:
export default renderOnlyOnMonday(() =>
<div>See me on Monday!!</div>
);
For recompose
Be extra aware that recompose APIs are functions that returns a HOC. That’s why their APIs are named with parentheses postfix. You can imagine withProps are implemented like this:
export const withProps = extraProps => BaseComponent => props =>
<BaseComponent {...props} {...extraProps} />;// Use it like this:
export default withProps({text: "Click Me"})(() =>
<div>See me on Monday!!</div>
);
The Migration
We’ve covered a lot pre-condition for our topic. Now it’s the time for the actual migration. Show me!
Wait, before we get started, I imagine you’re smart enough to figure out how already!
Think again what a recompose stack look like:
const enhance = R.compose(
HOC here
Another HOC here
Last HOC here
);
Then,
Simply replace the HOC in the recompose stack
with hand-written HOC implemented with React Hooks
Let’s try replacing withProps() first:
const enhance = R.compose(
BaseComponent => props => {
return <BaseComponent {...props} text="Click Me" />;
},
R.withStateHandler({ active: false }, {
onClick: ({ active }) => event => ({
active: !active,
}),
}),
connect(mapStateToProps, mapDispatchToProps) // react-redux
);
Then, we can do withStateHandlers():
const enhance = R.compose(
BaseComponent => props => {
return <BaseComponent {...props} text="Click Me" />;
},
BaseComponent => props => {
const [active, setActive] = useState(false);
const onClick = useCallback(() => {
setActive(!active);
}, [active, setActive]);
return <BaseComponent {...props} active={active} onClick={onClick} />;
},
connect(mapStateToProps, mapDispatchToProps) // react-redux
);
You may want to replace connect() with redux-react-hook later. Let’s imagine it’s done. The last step is to pull out compose
stack:
const enhance = BaseComponent => props => {
const [active, setActive] = useState(false);
const onClick = useCallback(() => {
setActive(!active);
}, [active, setActive]);
return <BaseComponent {...props}
text="Click Me"
active={active} onClick={onClick}
// and redux-react-hook props
/>;
};
Be aware that compose
are applied from right to left. The right most HOC becomes the inner most component in the React render tree. The left most HOC is the outer most one. Be careful about the props ordering and you’re good to go.
The last step of the migration is to pull the enhance body into your React component directly. Yay! We’re done.
Conclusion
You now understand:
- What’s a HOC?
- What’s a recompose HOC/recompose stack?
- Then, gradually replace HOCs one by another.
- Collapse the
compose
stack.
That’s all. Nothing fancy here. Just basic understanding on how JavaScript works and how React works.
After all, React is nothing fancy but just JavaScript.
Happy coding!