Blog Hero Background

Where did Hooks come from?

What exactly are they? Why did React adopt them? And are they better than class-based components?

User Avatar
Brad WestfallSay hi on Twitter
March 13, 2023

Upcoming Workshops

Hooks are for sharing common logic between components. To be clear, by "logic" we're not talking about the UI part (the JSX) of your component. We're talking about all the stuff in the component before the JSX. In class-based components we would have said it's in the lifecycle methods and custom methods. In function components its just the stuff above the JSX.

In a way, the story of Hooks is closely tied to the story of React and its previous APIs for sharing code. So bear with me while we go back to the beginning for a moment...

The First React API: 2013

tldr; React devs didn't like mixins, the first API for sharing logic.

Originally, React had a way to share common logic between your components called mixins. These were the early days of React before JavaScript had classes. These pseudo-class looking components allowed for "mixing in" sharable logic. At the time, mixins were blamed as a root cause of some anti-patterns that started to circulate the community. So most React developers cheered when the API for mixins went away in 2016 when React got real classes.

Class Components: 2016

tldr; Classes couldn't share logic so the community invented patterns to do so.

After JavaScript got classes in ES2015 (ES6), React soon followed with the class-component you can still use today. But if you're newer to React, you might wonder why it's widely said that you should avoid class-components altogether in React?

The main reason is that it's difficult to share logic. When we lost mixins, we also lost a primitive way to share code. You can share/reuse UI by making a new component to share JSX, but there was no built-in way to share lifecycle methods such as componentDidMount, componentDidUpdate, and componentWillUnmount.

These methods in particular are where you might want to manage your component's side-effects. So if you were to write ComponentOne with a certain side-effect, you would have to copy that logic to ComponentTwo and thus your logic is not abstracted in a way where you write it once.

Couldn't we just do inheritance?

class ComponentOne extends SharableStuff {
// ...
class ComponentTwo extends SharableStuff {
// ...

Nope. React does not let us write components that inherit from other components like this. Also, even if React did let you do this, how would you share multiple bodies of logic into ComponentOne? Multiple inheritance isn't allowed so this won't work:

class ComponentOne extends SharableStuffA extends SharableStuffB {
// ...

I bring this up because these are the common questions I get in my workshops when I teach Hooks to groups who have big object oriented backgrounds. Inheritance is where their intuition tells them to go to share methods between classes so I understand.

A React class must extend either React.Component or React.PureComponent and there are no APIs from React itself to share code.

The community is clever though. React developers created two patterns to effectively share code between components which are called Higher Order Components (Hoc) and Render Props.

HoC and Render Props

I'm not going to deep dive into these patterns in this post, but after we used these patterns for a while and beat them up on Twitter for about two years, it became apparent that the HoC pattern was problematic and confusing. The Render Props pattern fixed the main issues of HoC's but also was confusing and had some problems that were unique to it.

Stateless Function Components

Around this same time, the React team announces a new way to make components with just functions and not classes. The main idea at the time was to have a component that would just take props and could return JSX. There was no state or ability to use React API's similar to lifecycle methods of classes.

We called them Stateless Function Components because they also couldn't have state.

Before long, the React team was telling us to not call them that. We should call them Function Components because... they had plans 🤔

Hooks in 2018

I was lucky enough to be at the conference in late 2018 when Hooks were announced. I remember the vibe and the excitement. It was also a little intimidating. It kind of felt like my time invested in becoming a React expert was reset and we all had to figure out this new idea of whatever the heck a Hook is.

Essentially, Hooks are just a functions that we can call from within function components. You can use the built-in ones and write your own:

  • Built-in Hooks: These the APIs like useState() that give function components an ability to "hook into" all the features of React.
  • Custom Hooks: These are just functions that we write which implement the built-in Hooks. The general idea of a custom Hook is to create re-usable logic for any component that wants to use it.

As an example of each, React has useState() so function components can have their own local state similar to classes having state. But if you refresh the page, all local state is reset (just like any other JS variable would be). So you could make your own useLocalStorageState() which perhaps works just like useState() but also persists the state to localStorage for bringing the value back after refreshes.

Here's another example that uses custom hooks to share data-fetching logic. You don't have to fully understand how to use useState and useEffect, you just need to understand that they do some logic for my component and I want to share it. The highlighted code would need to be written again if another component wanted to also fetch products based on a productId:

function BrowseProducts({ productId }) {
const [product, setProduct] = useState(null)
useEffect(() => {
fetchProduct(productId).then((product) => setProduct(product))
}, [productId])
// return <div>...</div>

Here's the same logic moved to a custom Hook. Now any component can use the useFetchProduct Hook:

// Custom Hook
function useFetchProduct(productId) {
const [product, setProduct] = useState(null)
useEffect(() => {
fetchProduct(productId).then((product) => setProduct(product))
}, [productId])
return product
function BrowseProducts({ productId }) {
const product = useFetchProduct(productId)
// return <div>...</div>

This is an over-simplified example and the useEffect code above is incomplete. If you want a data-fetching custom Hook, I might recommend the custom hook from React Query called useQuery().

Today, you can still do classes if you wish. If you find them easier to use, that's totally up to you. However, you will have issues sharing logic between them. Even if you're okay with these issues and you don't find Hoc and Render Props confusing, you might find it difficult to collaborate or work in teams with other React developers who got started in the last 5 years. They came to React when Hooks were taught as the main way to do React. They might not know the "in's and out's" of class components, how to deal with their weird scoping issues with this, and how to decide when and how to use Hoc's or Render Props. Also, the vast majority of third-party libraries in the React ecosystem have moved away from Hoc and Render Props and have adopted hooks. So you won't be able to easily use their tools since Hooks only work with function components.

Some of my friends who have been doing React for a long time remember these conversations and the tradeoffs. One thing I've noticed though (at least on Twitter) is that history is repeating itself. There's a whole new generation of React developers who don't know this back-story and why we have Hooks in the first place. I admit, some parts of Hooks are more difficult than they were with classes like how we might need memoization (useMemo and useCallback), but it's a tradeoff. You can either use classes with HoC's and Render Props (not easy either) or use Hooks with an ability to easily share code but with the complexities of understanding memoization.

Thanks for reading. If you have questions about abstraction and class-components, please reach out via Twitter and I don't mind having the discussion with you.

Instead of having comments here in our blog, we've tweeted about this post so you can comment there if you wish.

View on Twitter

Photo by Thomas Millot on Unsplash

Subscribe for updates on public workshops and blog posts

Don't worry, we don't send emails too often.

i love react
© 2024