Use hooks in class components with ease
Tags: react, webdev, javascript
Published: 2020-10-20 20:02:26

The application I develop at work has been in development for a couple of years, meaning a lot of the code and structure is, sadly, built without hooks.

Although, sometimes we want to include new features to the older parts of the application. Features that are written with hooks.  

## No problem!
While we cannot use a hook inside a class component, we could use one of two patterns for code reuse that works with them: [Higher Order Components](https://reactjs.org/docs/higher-order-components.html) and [Render Props](https://reactjs.org/docs/render-props.html). And make the hook available through one of them.

> In the rest of this post a Higher Order Component will be referred to as a HOC to save me some keystrokes...

We can imagine a `useTodos()` hook that loads a list of Todos, and maybe some other stuff as well, that normally would be used like this:

```javascript
function TodosPage() {
   const { data, isLoading, error } = useTodos()
   if(isLoading) return <Spinner />
   /* etc. */ 
}
```
Now let us have a look at how to make this hook available with the two patterns:
## HOC
A higher order component is just a function that accepts a component as an argument that will receive some additional props. 
```javascript
function injectTodos(Component) {
  const InjectedTodos = function (props) {
    const todos = useTodos(props);
    return <Component {...props} todos={todos} />;
  };
  return InjectedTodos;
}
```
So we just make the function, it accepts the Component to enhance with all the todo information. Inside we make a function component that uses the hook and return that.

We name the function to make InjectedTodos appear in the dev tools instead of just returning that straight away, to make debugging easier.

Now we could do:
```javascript
class TodosPage extends React.Component {
  render() {
    const { data, isLoading, error } = this.props.todos;
    if(isLoading) return <Spinner />;
    /* etc. */
  }
}

export default injectTodos(TodosPage);
```

Great! 

### Now on to Render props
A render prop component basically hijacks the children properties, replacing that with a function that give you access to additional data or functions:
```javascript
function TodosData({children}) {
  const todos = useTodos()
  return children(todos)
}
```
And now we could put this to use like this:
```javascript
class TodosPage extends React.Component {
  render() {
    return (
      <TodosData>
        {({isLoading, data, error}) => {
          if(isLoading) return <Spinner />
          /* etc. */
        }
      </TodosData>
    )
  }
}
```
## To the **ease** part
So with not so many lines of code, we could make hooks available in *ye old* class components. But, imagine that we have several hooks that we would like to make available. We will kind of write the same wrappers again and again, and again to make the hook available through a render prop or a HOC.

To make this transformation easier we could write ourselves two utility functions to convert a hook to either a HOC or render prop.

So for a HOC:
```javascript
export function makeHOC(useHook, name) {
  return function (Component) {
    const HOC = function (props) {
      const hookData = useHook(props);
      const hookProps = { [name]: hookData }
      return <Component {...props} {...hookProps} />;
    };

    HOC.displayName = `${name}HOC`;
    
    return HOC;
  };
}
``` 

We simply wrap the code to make a HOC with a function that accepts the hook we want to use and what name the props property will be. 

I will forward any props to the hook so you could accept arguments to the hook that way. 

Also, we do the naming thing, but this time using the `displayName` property on our component.

Now to make HOCs of our hooks we simply do this:
```javascript
const injectTodos = makeHOC(useTodos, "todos")
const injectUsers = makeHOC(useUsers, "users")
```

And for the render prop:
```javascript
export function makeRenderProps(useHook, name) {
  const RenderProps = function ({ children, ...rest }) {
    const hookData = useHook(rest);
    return children(hookData);
  };

  if (name) RenderProps.displayName = `${name}RenderProps`;

  return RenderProps;
}
```
Same here, a function that accepts a hook, and optional name to appear in the dev tools. It will forward every prop, except the children, to the hook.  

And the creation of render props components:
```javascript
const TodosData = makeRenderProps(useTodos, "Todos")
const UsersData = makeRenderProps(useUsers, "Users")
```

### What about hooks that accepts multiple arguments?
Well yes, the above code do have some limitations. If the hook where to need several arguments, not from a single props object, that would not work.

If we were to make [the react query library hook `useQuery`](https://react-query.tanstack.com/docs/overview) available through a HOC or Render Props? That hook needs two arguments, an ID and a function returning a promise of data, and a third, optional, options argument. 

So we could either make a "wrapper" hook that accepts the props and returns the hook with the properties in the right place:

```javascript
function useWrappedQuery(props) {
  return useQuery(props.queryId, props.queryFn, props.queryOptions)
}
```

The `useWrappedQuery` could then be used by our `makeHOC` and `makeRenderProps` functions. 

Or the `makeHOC`/`makeRenderProps` functions could accept an additional, optional argument. A function that returns the arguments of the hook. Like this:

```javascript
export function makeHOC(useHook, name, convertProps = (props) => [props]) {
  return function (Component) {
    const HOC = function (props) {
      const hookData = useHook(...convertProps(props));
      const hookProps = { [name]: hookData }
      return <Component {...props} {...hookProps} />;
    };

    HOC.displayName = `${name}HOC`;
    return HOC;
  };
}
```

The `convertProps` function should return an array that will be spread to arguments in the hook. By the default it will return an array with the props as first, and only, argument. Same as the previous implementation. 

Now you could map props from the HOC/RenderProps argument to the hook:

```javascript
class TodoList extends React.Component { /*...*/ }

const injectQuery = makeHOC(
  useQuery, 
  "query", 
  props => [
    props.queryKey,
    props.queryFn,
    props.queryOptions
  ]
)

export default injectQuery(TodoList)
```

And use this like this
```javascript
const queryOptions = {retryDelay: 10000}

<TodoList 
  queryKey="toods"
  queryFn={apiClient.todos.get}
  queryOptions={queryOptions}
/>

```
Now the `TodoList` component has hook data available in the props `query` property.

**Or** we could also hard code the arguments with this function:

```javascript
const injectTodosQuery = makeHOC(
  useQuery,
  "todos",
  () => [
    "todos",
    apiClient.todos.get,
    queryOptions
  ]
}

/* etc. */
```

What ever solution you like to implement there is a way, and possibilities to "use" hooks inside class components.

> Photo by [Marius Niveri](https://unsplash.com/@m4r1vs?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/photos/rfg4l6_lu3c)