Skip Main Navigation
Ben IlegboduBen Ilegbodu

React custom Hooks vs. Mixins

How Mixins are surprisingly similar to custom Hooks for sharing stateful, non-visual logic

Sunday, February 21, 2021 · 4 min read

The last couple of weeks, I've been working on my latest project, NBA Player Tiers (very much still in-progress). I wrote this custom Hook to retrieve NBA player information from a Firestore DB. It's composed of other custom Hooks and it just makes me giddy.

import { useEffect, useState } from 'react'
import { usePromise as useSafeAsync } from 'react-use'

const getAllPlayers = async () => {
  // makes a Firebase request to Firestore
  // and returns a Promise with an array
  // of Player objects
}

const usePlayers = () => {
  const safeAsync = useSafeAsync()
  const [allPlayers, setAllPlayers] = useState([])

  useEffect(() => {
    safeAsync(getAllPlayers()).then(setAllPlayers)
  }, [safeAsync])

  return allPlayers
}

const PlayerPicker = () => {
  const players = usePlayers()

  // render players UI using `players`
}

The usePromise custom Hook from react-use (which I've renamed to useSafeAsync) returns a helper function. That function takes a Promise and will only resolve it when the component is mounted. If the component is unmounted, the Promise will not resolve. It's a safer way to do async within React components. If you're interested in the rationale behind this, read my earlier post called Handling async React component effects after unmount.

This usePlayers custom Hook is an extraction of the Firestore side effect request and state management. I use usePlayers() as if it's a regular helper method but the PlayerPicker component will re-render when the async data returns because usePlayers updates the component's state.

React has always been great at sharing UIs through its component model. And we can easily reuse traditional helper functions by importing them. But abstracting and sharing stateful, non-visual logic had always been in a challenge in React prior to custom Hooks.

Then the other day I saw this tweet from Mark Dalgleish that brought back nostalgia:

We used to share stateful, non-visual logic with Mixins. But Mixins were dropped 4 years ago with the release of React 15.5 and had fallen out of favor nearly 2 years prior. It's likely that there are many React developers who don't know much about Mixins at this point.

So I want to take a quick look walk down memory lane at Mixins to see how surprisingly similar they are to custom Hooks. Mixins just had more gnarly gotchas.


Mixins

Before Hooks, React components were created using native JavaScript classes. And before classes became a part of JavaScript with ES6/ES2015, React had its own class system. It still exists with the create-react-class package. Mixins were plain objects that implemented React lifecycle methods and were "mixed into" a component using React.createClass.

Five years ago, the equivalent of the useSafeAsync and usePlayers Hooks as Mixins could've looked something like:

var SafeAsyncMixin = {
  // Use `_isMounted` property to keep track
  // of mounted state
  _isMounted: false,

  componentDidMount: function () {
    this._isMounted = true
  },

  componentWillUnmount: function () {
    this._isMounted = false
  },

  // A helper method for the component to "safely"
  // update the state based on the mounted state
  safeAsync: function (promise) {
    return new Promise(function (resolve) {
      return promise.then(function (value) {
        if (this._isMounted) {
          resolve(value)
        }
      })
    })
  },
}

function getAllPlayers() {
  // makes a Firebase request to Firestore
  // and returns a Promise with an array
  // of Player objects
}

var PlayersMixin = {
  mixins: [SafeAsyncMixin],

  getInitialState: function () {
    return {
      allPlayers: [],
    }
  },

  componentDidMount: function () {
    // Use `safeAsync` method provided by `SafeAsyncMixin`
    this.safeAsync(getAllPlayers()).then(function (allPlayers) {
      return this.setState({ allPlayers: allPlayers })
    })
  },
}

var PlayerPicker = React.createClass({
  mixins: [PlayersMixin],

  render: function () {
    // `this.state.allPlayers` comes from
    // `PlayersMixin`
    const players = this.state.allPlayers

    // render players UI using `players`
  },
})

It was a struggle forgetting all my modern JavaScript knowledge and writing this in ES5. I highly doubt I would've written the code this well 5 years ago. 😂

So Mixins worked okay. They specifically existed to be able to extract component logic, but they were ultimately considered harmful. Mixins relied heavily on indirection and implicit agreements to work, as we see above.

The PlayerPicker component has an agreement with the PlayersMixin that it will provide the allPlayers state. Similarly, PlayersMixin expects to have the safeAsync helper method from SafeAsyncMixin. We used to define many Mixins that expected the host component to define a property or method that they would use. Without enforcement of these agreements, when a component used many Mixins, the whole system was very fragile to change.

All Mixins of a component were also playing in the same playground. Any of the properties or methods a mixin created ultimately were properties and methods of the component. The PlayerPicker component above would have the _isMounted property as well as the safeAsync helper method. So if two Mixins happened to use the same names, collisions would occur without any warning or protection. The only way to avoid collisions was to namespace everything with the mixin name to ensure that the names were unique.

But Mixins ultimately were nixed because ES6 classes didn't support them. With React 15, the React team wanted to move to using ES6 classes instead of maintaining a class system. They could piggyback on all the improvements coming to the language.

Custom Hooks

A lot happened in the three years between React 15 with ES6 classes and React 16.8 with Hooks. We tried to repurpose Higher-Order Components (HOCs) and Render Props to solve the problem of sharing stateful, non-visual logic. But they weren't designed to solve the problem like Mixins had been. And Mixins and Hooks are surprisingly similar. Let's look at the Hooks implementation again.

import { useEffect, useState } from 'react'
import { usePromise as useSafeAsync } from 'react-use'

const getAllPlayers = async () => {
  // makes a Firebase request to Firestore
  // and returns a Promise with an array
  // of Player objects
}

const usePlayers = () => {
  const safeAsync = useSafeAsync()
  const [allPlayers, setAllPlayers] = useState([])

  useEffect(() => {
    safeAsync(getAllPlayers()).then(setAllPlayers)
  }, [safeAsync])

  return allPlayers
}

const PlayerPicker = () => {
  const players = usePlayers()

  // render players UI using `players`
}

The usePlayers Hook and the PlayersMixins are pretty similar. In a way, custom Hooks are Mixins done well.

The main difference between the two is that dependencies are explicit with Hooks versus implicit with Mixins. The allPlayers data available in PlayerPicker comes from calling usePlayers, instead of magically being in state because it mixed in PlayersMixin. Similarly, usePlayers explicitly gets the safeAsync function from useSafeAsync as opposed to PlayersMixin implicitly receiving the safeAsync method from SafeAsyncMixin.

Custom Hooks also are not leaky. The allPlayers name that usePlayers uses for its state doesn't impact the PlayerPicker component. Hooks use the input/output interface of regular functions so that there are no name collisions.

These two key differences allow a library like react-use to exist because a component can confidently use many custom Hooks without worrying about how they may interact with each other. Mixins are the complete opposite. Many Mixins helped reduce code duplication but made the component increasingly more fragile.

Keep learning my friends. 🤓

Subscribe to the Newsletter

Get notified about new blog posts, minishops & other goodies


Hi, I'm Ben Ilegbodu. 👋🏾

I'm a Christian, husband, and father of 3, with 15+ years of professional experience developing user interfaces for the Web. I'm a Google Developer Expert Frontend Architect at Stitch Fix, and frontend development teacher. I love helping developers level up their frontend skills.

Discuss on Twitter // Edit on GitHub