Skip to content

Using Hooks in Classes


One of the things I love about React is that the team behind it makes upgrading so easy (thanks, React folks). With React Hooks, it's no different, we can start using these new APIs without having to change all of our existing code.

One thing to know about hooks is that you can't use them in class components, only functions. So what happens when you create a new custom hook and find that you need to use it in a component class?

You can wrap it! Check out our old friend useDocumentTitle.

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title
  }, [title])
}

We can use it in a function component just fine:

function Invoice(invoice) {
  useDocumentTitle(`Invoice ${invoice.number}`)
  // etc.
}

But before hooks it probably would have looked something like this:

class Dashboard extends React.Component {
  render() {
    return (
      <Fragment>
        <DocumentTitle title="Dashboard" />
        <div>
          {/* etc. */}
        </div>
      </Fragment>
    )
  }
}

But check it out, there's nothing stopping us from wrapping our hook in a component that works that very same way, we just have to send the props to the hook like so:

function DocumentTitle({ title }) {
  useDocumentTitle(title)
  return null
}

If you wanna be obtuse and force your team to learn more JavaScript than they probably want to know, you could write it like this too.

let DocumentTitle = p => (useDocumentTitle(p.title), null)

If your hook has a return value, render props work great, consider a hook that gives you the mouse position:

function useMousePosition(listen=true) {
  let [ pos, setPos ] = useState({ x: 0, y: 0 })
  useEffect(() => {
    if (listen) {
      let handler = event => {
        setPos({ x: event.clientX, y: event.clientY })
      }
      window.addEventListener('mousemove', handler)
      return () => {
        window.removeEventListener('mousemove', handler)
      }
    }
  }, [listen])
  return pos
}

We'd wrap it like before, passing props as args to the hook, but now we store the return value and pass that to the children render prop:

function MousePosition({ listen, children }) {
  let pos = useMousePosition(listen)
  return children(pos)
}

Now class components can use this hook as a render prop:

class SoOldFrom4MonthsAgo extends React.Component {
  render() {
    return (
      <MousePosition>
        {pos => (
          // etc.
        )}
      </MousePosition>
    )
  }
}

So don't sweat it!

If you're authoring libraries, I highly recommend you ship a render prop, and maybe even a higher order component version of your hooks. While a project may be able to use hooks, not all of its components will be able to. As a community, we shouldn't expect everybody to be migrating their classes to hooks--no matter what that guy said at React Conf.

We're going to be digging into React from a fresh new hooks perspective with strategies like this in our new workshop this spring, we'd love to see you there :D

Loading...

While we don't have blog comments, we tweeted about this posting when it went live so we welcome your comments there:


React Router

Michael Jackson and Ryan Florence create the React libraries that you use in your apps like React Router and Reach UI. All of our trainers are experts in React and JavaScript so let us share our knowledge with you and your team!

I Love React
© React Training 2019