A bridge between React Class components and Hooks
Two patterns to benefit from React hooks in your React class components.
One of the great features of React is its component-based nature. Originally, function and class components coexisted and there were clear usages for both. Function components would be the most basic reusable components with no other logic than returning JSX. On the other hand, class components were intended to be more complex components encapsulating additional logic than just rendering JSX, for instance, managing state and running lifecycle methods.
However, after the addition of React hooks, this clear difference among them immediately disappeared. Hooks allow the use of state and other React features without writing a class component. Moreover, hooks don’t work within class components. State and lifecycle methods have their hook counterpart making it really easy to migrate from class to function components and benefit from the higher simplicity and readability that function components and hooks offer.
Before hooks, a common pattern to add logic into class components was to wrap them with high-order functions. A well-known example was connect()
from React-Redux. Until recently, it was pretty common to see multi-wrapped components looking like this.
export default compose( withContext, withLogging, connect(mapStateToProps, mapDispatchToProps),)(MyComponent);
For Flow-typed applications, these composed patterns increased the complexity to correctly Flow-type them.
Class components are still an option when building a new component. However, increasingly, the benefits of hooks are defining a path where hooks and function components would predominate. Class components are not going away but there’s a strategy for `gradual adoption` for hooks in React. As the React team says,
At Facebook, we have tens of thousands of components written as classes, and we have absolutely no plans to rewrite them. Instead, we are starting to use Hooks in the new code side by side with classes.
Teams might gradually refactor class into function components. However, class ones will stay in the codebase for a while. Because rewriting into function components might be out-of-scope many times, there can be different strategies to take advantage of hooks while keeping your class components.
The Wrapper Strategy
The Wrapper strategy consists of wrapping your class component with a function component. The hook gets called inside the function component and the returned value gets passed to the class component. See the following simple example to illustrate the case.
// Simple class component that takes a boolean prop.type Props = {| isUserProfile: boolean |}class MyComponent extends Component<Props> { render() { return isUserProfile
? <p>Welcome back!</p>
: null;
};export default MyComponent;
Instead of using connect()
, now we can use useSelector. The class component will still get the boolean prop along with the new userData
prop from the hook.
type WrapperProps = {| isUserProfile: boolean |}type HookProps = {|
userData: {| name: string |}
|};type Props = {|
...Wrapper,
...HookProps,
|};class MyComponent extends Component<Props> { render() { const { isUserProfile, name } = this.props; return isUserProfile
? <p>Welcome back, ${name}</p>
: null; }
};function MyComponentWrapper(props: WrapperProps) { const userData = useSelector(userData); return <MyComponent {...props} userData={userData} />;}MyComponentWrapper.displayName = "MyComponent"export default MyComponentWrapper;
The Render Props Strategy
The Render Props strategy consists of creating a function component that follows the Render Props strategy. (TADA!) In this case, the function component renders a children prop whose value is a function that returns a React element. This React element receives the hook data via the parameter in the function. I like naming these ones Consumers, but don’t confuse them with React Context’s Consumers.
See the following simple example to illustrate the case.
type Props = <{| isUserProfile: boolean |};type UserDataType = {| name: string |}type ConsumerProps = {| children: ({| userData: UserDataType |}) => ?React.Node,|};class MyComponent extends Component<Props> { render() { const { isUserProfile } = this.props; return ( <MyComponentConsumer> {({ userData }) => (
isUserProfile ? <p>Welcome back, ${userData.name}</p>
: null
)} </MyComponentConsumer> ) }};function MyComponentConsumer ({ children }: ConsumerProps) { const userData = useSelector(userData); return children({ userData });}MyComponentConsumer.displayName = "MyComponent"export default MyComponentConsumer;