DEV Community

Cover image for How to Use a React Hook in a Class Component
Nick Taylor
Nick Taylor

Posted on • Updated on • Originally published at iamdeveloper.com

How to Use a React Hook in a Class Component

Did you know that you can use hooks in class components?

OK, I'm lying, kind of. You can't use a hook directly in a class component, but you can use a hook in a wrapped function component with a render prop to achieve this.

Before going ahead with this, if you're able to convert your class component to a function component, prefer that. But if the component needs to remain a class component for whatever reason, this pattern works great. You will most likely encounter this scenario when working on a mature React codebase.

The beauty of this pattern is that you can build new components as function components using hooks. Class components that can't be upgraded for whatever reason benefit from the same functionality via a thin compatibility layer, the wrapper component.

Let's first create a hook.

import { useEffect, useState } from "react";

export function useDarkMode() {
  // Taken from https://usehooks.com/useDarkMode/

  // For this to persist, we'd use localStorage or some other kind
  // of way to persist between sessions.
  // see e.g. https://usehooks.com/useLocalStorage/
  const [enabledState, setEnabledState] = useState(false);
  const enabled = enabledState;

  useEffect(() => {
    const className = "dark-mode";
    const element = document.body;
    if (enabled) {
      element.classList.add(className);
    } else {
      element.classList.remove(className);
    }
  }, [enabled]);
  return [enabled, setEnabledState];
}
Enter fullscreen mode Exit fullscreen mode

Now let's create a function component that has a render prop. Note that the prop does not literally need to be called render, but it tends to convey its purpose.

// I wouldn't normally call a component something like this.
// It's just to convey what it is doing for the purpose of the article
const UseDarkModeHookWrapperComponent = ({ render }) => {
  const [darkMode, setDarkMode] = useDarkMode(false);

  // Uses the render prop called render that will expose the value and
  // setter for the custom hook
  return render(darkMode, setDarkMode);
};
Enter fullscreen mode Exit fullscreen mode

And now, let's use the wrapper component in a class component.

export default class App extends Component {
  render() {
    return (
      <UseDarkModeHookWrapperComponent
        render={(darkMode, setDarkMode) => {
          return (
            <div
              style={{
                display: "grid",
                gridTemplateColumns: "200px",
                gap: "2rem",
                maxWidth: "50%",
                placeItems: "center"
              }}
            >
              <ThemeToggler darkMode={darkMode} setDarkMode={setDarkMode} />
              hello
            </div>
          );
        }}
      />
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

And voilà! You're using your hook in a class component. Here's the complete application in action.

If you want to see a real-world example, look no further than the Forem codebase. Here's the useMediaQuery hook, and here's the wrapper component. If you want to see it in action, it's called in the ReadingList component.

Photo by Jamie Matociños on Unsplash

Latest comments (5)

Collapse
 
rzs401 profile image
Richard Smith

Thanks for this.

Collapse
 
jdnichollsc profile image
J.D Nicholls

It would be great to analyze the performance of this strategy because we're using a HOC to be able to inject a hook in a class component, so in every render that node is mounted again as I know, thanks for sharing this crazy experiment!

Collapse
 
johnsawiris profile image
John Sawiris

Love it 😀

Collapse
 
nickytonline profile image
Nick Taylor

Kylo Ren on Undercover Boss giving a thumbs up

Collapse
 
jzombie profile image
jzombie

+1 Having also done this before it's kind of fun to say, "I've even used hooks inside of class components."