A critical look at React Hooks

Posted January 12, 2022

This is a quick article in ambient reply to @holtbt take on React Hooks.

Disclaimer: I did not listen to the full podcast, I only listened to this small clip from @swyx posted

The following is @holtbt's take on React Hooks from this podcast

"My unpopular opinion is that I do not like React Hooks. And if you get people from the React community, there's going to be some people that are legitimately going to be upset by that.

I think they demo really well. And the first time you showed us some of that, it's just amazing and fascinating. But maintaining large code bases full of hooks just quickly devolves into a performance mess. You get into weird edge cases.

And long term, I think they actually have more cognitive load because you have to understand closures really well to understand hooks very well, whereas [the opposite is true for class components]. You have to understand this and context a little bit, but not a lot.

I actually really appreciate Brian's take here. He is not wrong and I share some similar opinions (though, spoiler, overall I like React Hooks).

The bad

I think the biggest issue he mentions is that, in order to really understand React hooks, you have to really understand closures and references.

And this isn't something to just brush past. Yes, it's important to understand these programming concepts but the degree of awareness required to use hooks is something I've never seen before.

And for me, this really defines what using the React runtime feels like at its worst:

  • hyperawareness of all objects and arrays created during render
  • learning the ins and outs of memoization
  • building up judgement for when memo
  • simultaneously fearing premature pre-optimization and bad performance
  • hacking around with refs to get stable values
  • defensively programming against unstable values
  • serializing and hashing then worrying about performance of hashing methods

and the list goes on.

This is not conventional JavaScript. Hooks, the rules of hooks, and the React runtime itself is not conventional JavaScript and the presence of a specialized linter proves this.

I respectfully reject claims that "React is just JavaScript". I don't think we are communicating the right expectations when we make those claims because learning React is more than learning a JavaScript library.

The great

On the flip side (and why I still like hooks), Hooks allow for easy composition. This also isn't something to brush past.

Just to serve a counter-opinion to those expressed in the replies here: I freaking love hooks. They're not perfect, but so many of the innovations the react community has come up with over the last three years would never have happened if it weren't for hooks.

by @kentcdodds

This ease of composition has fundamentally changed the React ecosystem and has enabled many many new and great new libraries and additions:

to name a few.

Prior to hooks, you had two choices for composition:

  1. HOCs and/or render props
  2. Put it in Redux. E.g. connected-react-router

Render props were too complex when using more than two or three of these and HOCs didn't enable other HOCs to share inputs easily.

class MyComponent extends Component {
  render() {
    const { fourthValue } = this.props;

    return (
      // these are render-props
      <FirstOne>
        {(firstValue) => (
          <SecondOne input={firstValue}>
            {(secondValue) => (
              <ThirdOne>
                {(thirdValue) => (
                  <button>
                    {firstValue} {secondValue} {thirdValue} {fourthValue}
                  </button>
                )}
              </ThirdOne>
            )}
          </SecondOne>
        )}
      </FirstOne>
    );
  }
}

// this is an HOC
function withFourth(InputComponent) {
  const sharedThing = createShared();

  return class WrapperComponent extends Component {
    render() {
      return <InputComponent {...this.props} fourthValue={sharedThing} />;
    }
  };
}

export default withFourth(MyComponent);

That gets replaced with this:

function useFourth() {
  const sharedThing = useMemo(createShared, []);
  return sharedThing;
}

function MyComponent() {
  const firstValue = useFirst();
  const secondValue = useSecond(firstValue);
  const thirdValue = useThird(secondValue);
  const fourthValue = useFourth();

  return (
    <button>
      {firstValue} {secondValue} {thirdValue} {fourthValue}
    </button>
  );
}

And as a result of this ease of composition, people don't put everything in Redux anymore.

There is much less need for global state because other global stores can be composed in the component.

For example, take a look at how Redux and React Router compose together with nothing more than hooks

import { useParams } from 'react-router';
import { useSelector } from 'react-redux';

function Example() {
  const { slug } = useParams();
  // notice how this hooks use the input from the above hook!
  const data = useSelector((state) => state.pages[slug]);

  return <>{data?.title}</>
}

So, do I like hooks? Yes.
Do I wish I didn't have to think so much about memos? Absolutely.

Maybe something like React Forget will change things? 🤞

Thanks, that's all I got on this.