In React Conf 2018, the React team proposed Hooks.

If you want to know what Hooks are and what problems they solve, check out our introduction to understand React Hooks.

At first you may not like Hooks:

They are like pieces of music that you have to listen to carefully a few times to love:

As you read the documentation, don’t miss the important part — create your own Hooks! Too many people get caught up in arguing against us that class is expensive to learn and miss the bigger point of Hooks, which, like functional mixins, let you create and build your own Hooks.

Hooks was inspired by some existing technology, but I didn’t know that until Sebastian shared his ideas with the team. Unfortunately, the connection between these apis and those in use today is easily overlooked, and in this article I hope to help more people understand the more controversial points of the Hooks proposal.

The next section requires you to know the Hook APIuseStateAnd how to write custom hooks. If you don’t understand, check out the earlier link.

(Disclaimer: The opinions expressed in this article are personal and not related to the React team. The topic is big and complex, and there may be wrong opinions.)


It may be shocking at first when you learn that Hooks rely on fixed order calls to rerender, as explained here.

The decision was obviously controversial, which is why there was some opposition to our proposal. We will publish the proposal at the appropriate time, when we feel that the documentation and lectures describe it well enough.

If you are looking at some points in the Hooks API, I suggest you read Sebastian’s full response to the 1000+ Comments RFC, it’s clear enough but it’s so full that I’ll probably turn every paragraph of the comments into my own blog post. (Actually, I already did it once!)

I’m going to focus on a specific part today. As you may recall, each Hook can be used multiple times within a component. For example, we can declare multiple states with useState:

function Form() {
  const [name, setName] = useState('Mary');              // State variable 1
  const [surname, setSurname] = useState('Poppins');     // State variable 2
  const [width, setWidth] = useState(window.innerWidth); // State variable 3

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  });

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurnameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <>
      <input value={name} onChange={handleNameChange} />
      <input value={surname} onChange={handleSurnameChange} />
      <p>Hello, {name} {surname}</p>
      <p>Window width: {width}</p>
    </>
  );
}
Copy the code

Note that we use array destruct syntax to name the state variables returned by useState(), but these variables are not wired to the React component. In contrast, in this example React treats name as the “first state variable,” surname as the “second state variable,” and so on. They are called sequentially during rerendering to ensure that they are correctly identified. This article explains the reasons in detail.

On the surface, relying on sequential calls just feels wrong, and intuition is a useful signal, but it can sometimes mislead us — especially when we haven’t fully digested the puzzle. In this article, I will mention several schemes that are frequently proposed to fix Hooks and the problems they have.


This article will not be exhaustive, as you can see, we have looked at dozens to hundreds of different alternatives, and we are always considering alternative component apis.

Blogs like this are tricky because even if you have a hundred alternatives, someone will force one out and say, “Ha ha, you didn’t think of that!”

In practice, the issues raised by the different alternatives are quite repetitive, so instead of listing all the suggested apis (which would take months), I’ll show you the most common problems with a few concrete examples, and more questions will test your ability to draw parallels. 🧐

This is not to say that Hooks are perfect, but once you understand the flaws of other solutions, you will probably see that Hooks’ design makes sense.


Bug #1: Custom hooks cannot be extracted

Surprisingly, most alternatives don’t mention Custom hooks at all. It’s probably because we didn’t emphasize custom hooks enough in the Motivation document, but that’s hard to do until you understand the hooks fundamentals. Like the chicken and egg issue, but custom hooks are largely the focus of the proposal.

For example, if an alternative is to limit the number of useState() calls a component makes, you could put state in an object. Wouldn’t it be better to have class compatibility as well?

function Form() {
  const [state, setState] = useState({
    name: 'Mary'.surname: 'Poppins'.width: window.innerWidth,
  });
  // ...
}
Copy the code

To be clear, Hooks allow this style, you don’t have to split state into a bunch of state variables (see the advice in the FAQ).

But the key to supporting multiple calls to useState() is that you can extract part of the stateful logic from the component into custom hooks while using local state and effects alone:

Const [name, setName] = useState('Mary'); function Form() {const [name, setName] = useState('Mary'); const [surname, setSurname] = useState('Poppins'); // We move part of state and effects to custom hook const width = useWindowWidth(); / /... } function useWindowWidth() {// Define state variables in custom hook const [width, setWidth] = useState(window.innerWidth); useEffect(() => { // ... }); return width; }Copy the code

If you only allow useState() to be called once per component, you lose the ability to introduce state with Custom hooks, which is the key to custom hooks.

Bug #2: Naming conflicts

A common recommendation is to have useState() receive a unique identifier for key arguments (string, etc.) within the component to distinguish state variables.

It’s a little off the mark, but it looks something like this:

// ⚠️ This is NOT the React Hooks API
function Form() {
  // We pass several state keys to useState()
  const [name, setName] = useState('name');
  const [surname, setSurname] = useState('surname');
  const [width, setWidth] = useState('width');
  // ...
Copy the code

This tries to get rid of the dependence on sequential calls (to display keys), but introduces another problem — naming conflicts.

Of course, you may not be able to call useState(‘name’) twice in the same component except for errors. This can happen by chance due to any other bug, but when you use a Custom hook, you will always want to add or remove state variables and effects.

In this proposal, every time you add a new state variable to a Custom hook, you run the risk of breaking any component that uses it (directly or indirectly) because a variable with the same name may already be inside the component.

This is an API that is not optimized for change, the current code may always look “elegant”, but it is vulnerable to changing requirements, and we should learn from our mistakes.

In practice, the Hooks proposal solves this problem by relying on sequential calls: even if both Hooks use the name state variable, they are isolated from each other, getting separate “units of memory” for each call to useState().

There are other ways to address this flaw, but they have their own limitations. Let’s explore this question further.

Bug #3: You cannot call the same Hook twice

Another derivative proposal to “add a key” to useState is to use something like Symbol, so there is no conflict, right?

// ⚠️ This is NOT the React Hooks API
const nameKey = Symbol(a);const surnameKey = Symbol(a);const widthKey = Symbol(a);function Form() {
  // We pass several state keys to useState()
  const [name, setName] = useState(nameKey);
  const [surname, setSurname] = useState(surnameKey);
  const [width, setWidth] = useState(widthKey);
  // ...
Copy the code

This proposal appears to work with the extracted useWindowWidth Hook:

// ⚠️ This is NOT the React Hooks API function Form() {//... const width = useWindowWidth(); / /... } /********************* * useWindowWidth.js * ********************/ const widthKey = Symbol(); function useWindowWidth() { const [width, setWidth] = useState(widthKey); / /... return width; }Copy the code

But if you try to extract input handling, it will fail:

// ⚠️ This is NOT the React Hooks API function Form() {//... const name = useFormInput(); const surname = useFormInput(); / /... return ( <> <input {... name} /> <input {... surname} /> {/* ... */} </> ) } /******************* * useFormInput.js * ******************/ const valueKey = Symbol(); function useFormInput() { const [value, setValue] = useState(valueKey); return { value, onChange(e) { setValue(e.target.value); }}; }Copy the code

(I admit that useFormInput() Hook isn’t particularly useful, but you can imagine it handling things like validation and dirty State flags like Formik.)

Can you spot this bug?

We call useFormInput() twice, but useFormInput() always calls useState() with the same key, like this:

  const [name, setName] = useState(valueKey);
  const [surname, setSurname] = useState(valueKey);
Copy the code

We clashed again.

In practice the Hooks proposal does not have this problem because each call to useState() gets a separate state. Relying on fixed sequential calls frees us from worrying about naming conflicts.

Defect #4: Diamond problem (multi-level inheritance problem)

This is technically the same flaw as the last one, but it’s notorious enough to be mentioned, even on Wikipedia. (Sometimes referred to as the “Deadly Diamond of Death” — Cool!)

Our own mixin system is compromised.

For example, useWindowWidth() and useNetworkStatus() are supported by custom hooks and scription like useSubscription(), which are adapted to following functions:

function StatusMessage() {
  const width = useWindowWidth();
  const isOnline = useNetworkStatus();
  return (
    <>
      <p>Window width is {width}</p>
      <p>You are {isOnline ? 'online' : 'offline'}</p>
    </>
  );
}

function useSubscription(subscribe, unsubscribe, getValue) {
  const [state, setState] = useState(getValue());
  useEffect(() => {
    const handleChange = () => setState(getValue());
    subscribe(handleChange);
    return () => unsubscribe(handleChange);
  });
  return state;
}

function useWindowWidth() {
  const width = useSubscription(
    handler => window.addEventListener('resize', handler),
    handler => window.removeEventListener('resize', handler),
    () => window.innerWidth
  );
  return width;
}

function useNetworkStatus() {
  const isOnline = useSubscription(
    handler => {
      window.addEventListener('online', handler);
      window.addEventListener('offline', handler);
    },
    handler => {
      window.removeEventListener('online', handler);
      window.removeEventListener('offline', handler);
    },
    () => navigator.onLine
  );
  return isOnline;
}
Copy the code

This is a real working example. Custom Hook authors should be safe preparing or stopping using another Custom hook without worrying about whether it has already been “used” somewhere in the chain.

(As a counter example, the legacy React createClass() mixins don’t allow you to do this, and sometimes you’ll have two mixins that are both exactly what you want, but are incompatible due to extending the same “base” mixin.)

Here’s our diamond: 💎

/ useWindowWidth() \ / useState() 🔴 Clash Status useSubscription() \ useNetworkStatus()/useEffect() 🔴 ClashCopy the code

Relying on fixed sequential calls naturally solves it:

/ useState ✅ ()#1. State/ useWindowWidth() -> useSubscription()/useEffect() ✅#2. Effect
Status                         
      \                                          / useState()  ✅ #3. State
       \ useNetworkStatus() -> useSubscription()
                                                 \ useEffect() ✅ #4. Effect
Copy the code

Function calls do not have a “diamond” problem because they form a tree structure. 🎄

Bug #5: The copy-and-paste idea gets messed up

Perhaps we can salvage the “key” proposal for state by introducing some sort of namespace, and there are several different ways to do this.

One way is to use closures to isolate state keys. This requires you to “instantiate” custom hooks by wrapping each hook in a layer of function:

/ * * * * * * * * * * * * * * * * * * * * useFormInput. Js * * * * * * * * * * * * * * * * * * * / function createUseFormInput () {/ / every instantiated only const valueKey = Symbol(); return function useFormInput() { const [value, setValue] = useState(valueKey); return { value, onChange(e) { setValue(e.target.value); }}; }}Copy the code

This is cumbersome, and one of the design goals of Hooks is to avoid using deep nested functions of higher-order components and render props. Here, we have to “instantiate” any custom Hooks we use — and only use the produced functions once in the component body, which is a lot more trouble than calling Hooks directly.

In addition, you have to do it twice to get the component to use the Custom Hook. Once at the top level (or in a function when writing a Custom hook), and once for the final call. This means you have to jump back and forth between the top-level declaration and the render function for even a small change:

// ⚠️ This is NOT the React Hooks API
const useNameFormInput = createUseFormInput();
const useSurnameFormInput = createUseFormInput();

function Form() {
  // ...
  const name = useNameFormInput();
  const surname = useNameFormInput();
  // ...
}
Copy the code

You also need very precise naming, always consider “two-tier” naming — factory functions like createUseFormInput and instance Hooks like useNameFormInput and useSurnameFormInput.

If you call the same Custom hook “instance” twice at the same time, you will have a state conflict. In fact, the code above is just such an error — notice that? It should be:

  const name = useNameFormInput();
  const surname = useSurnameFormInput(); // Not useNameFormInput!
Copy the code

These problems are not insurmountable, but I think they would be more of a drag than following the Hooks rules.

Importantly, they break the copy-and-paste calculus. Custom hooks can still be used without a wrapper, but they can only be called once (which can be problematic when used). Unfortunately, when an API seems to be working, once you realize that there is a conflict somewhere in the chain, you have to wrap up everything that is defined.

Bug #6: We still need a code review tool

There’s another way to use the key state to avoid collisions, and if you know that, you might get really angry because I don’t like it, sorry.

The idea is to combine a key each time you write a Custom hook, like this:

// ⚠️ This is NOT the React Hooks API function Form() {//... const name = useFormInput('name'); const surname = useFormInput('surname'); / /... return ( <> <input {... name} /> <input {... surname} /> {/* ... */} </> ) } function useFormInput(formInputKey) { const [value, setValue] = useState('useFormInput(' + formInputKey + ').value'); return { value, onChange(e) { setValue(e.target.value); }}; }Copy the code

I like this least than any of the other alternatives. I don’t think it’s worth much.

After a Hook is called many times or conflicts with other hooks, the code may accidentally produce non-unique or synthesize invalid keys for transmission. Even worse, if it happens under certain conditions (we’ll try to “fix” it, right?). It may take some time before a conflict occurs.

We would like to remind you that all custom hooks marked by keys are vulnerable, not only because they increase the workload of the runtime (don’t forget that they convert to keys), but also because they gradually increase the bundle size. But if we had to be reminded of one problem, which one would it be?

If you had to declare state and effects in a conditional, this might work, but in past experience I’ve found it confusing. In fact, I don’t remember anyone defining this.state or componentMount in a conditional.

What does this code actually mean?

// ⚠️ This is NOT the React Hooks API
function Counter(props) {
  if (props.isActive) {
    const [count, setCount] = useState('count');
    return (
      <p onClick={() => setCount(count + 1)}>
        {count}
      </p>;
    );
  }
  return null;
}
Copy the code

Is count preserved when props. IsActive is false? Or reset count because useState(‘count’) was not called?

What happens to effect if the condition is to preserve state?

// ⚠️ This is NOT the React Hooks API function Counter(props) {if (props. IsActive) {const [count, setCount] = useState('count'); useEffect(() => { const id = setInterval(() => setCount(c => c + 1), 1000); return () => clearInterval(id); } []); return ( <p onClick={() => setCount(count + 1)}> {count} </p>; ) ; } return null; }Copy the code

No doubt it won’t run before props. IsActive is true the first time, but will it stop running once it is true? Will the interval reset when props. IsActive is set to false? If so, it is puzzling that Effect behaves differently than state(when we say not reset). If effect continues to run, then the if in the outer layer of effect no longer controls effect, which is also confusing. Didn’t we say we wanted conditional-controlled Effects?

If we do not “use” state during rendering but it is reset, what happens if there are multiple if branches containing useState(‘count’) but only one of them will be running at a given time? Is this valid code? If our core idea is to “distribute by key,” why throw it away? Does the developer want to return earlier from the component after that to reset all the states? If we really need to reset state, we can make it explicit by extracting the component:

function Counter(props) {
  if (props.isActive) {
    // Clearly has its own state
    return <TickingCounter />;
  }
  return null;
}
Copy the code

This might be a “best practice” for solving these puzzles anyway, so no matter which way you choose to interpret it, I find the semantics of declaring state and effect in conditions weird and you might feel it unconsciously.

If there’s one more caveat – the need to combine keys correctly can become a “burden,” it doesn’t get us anywhere. However, dropping this requirement (and going back to the original proposal) does give us something, which enables component code to be safely copy-pasted into a Custom hook with no namespace, reduced bundle size, and a slight efficiency gain (no Map lookup required).

Slowly understand.

Bug #7: Cannot pass values between Hooks

Hooks have the best feature of passing values between them.

Here is a simulated example of selecting a message recipient, which shows whether the currently selected friend is online:

const friendList = [ { id: 1, name: 'Phoebe' }, { id: 2, name: 'Rachel' }, { id: 3, name: 'Ross' }, ]; function ChatRecipientPicker() { const [recipientID, setRecipientID] = useState(1); const isRecipientOnline = useFriendStatus(recipientID); return ( <> <Circle color={isRecipientOnline ? 'green' : 'red'} /> <select value={recipientID} onChange={e => setRecipientID(Number(e.target.value))} > {friendList.map(friend =>  ( <option key={friend.id} value={friend.id}> {friend.name} </option> ))} </select> </> ); } function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); const handleStatusChange = (status) => setIsOnline(status.isOnline); useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }Copy the code

When changing the recipient, the useFriendStatus Hook unsubscribes from the previous friend and subscribes to the next one.

This works because we can pass the value returned by useState() Hook to useFriendStatus() Hook:

  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);
Copy the code

It is useful to pass values between Hooks. For example: React Spring can create a trailing animation where multiple values “follow” each other:

  const [{ pos1 }, set] = useSpring({ pos1: [0.0].config: fast });
  const [{ pos2 }] = useSpring({ pos2: pos1, config: slow });
  const [{ pos3 }] = useSpring({ pos3: pos2, config: slow });
Copy the code

(This is demo.)

Proposals to add a default parameter to Hook initialization or to write the Hook in a decorator form are difficult to implement.

You cannot easily pass values between Hooks without calling them from within a function. You can change the structure of these values so that they don’t need to be passed between multiple layers of components, and you can use useMemo to store the results. But you can’t reference these values in Effects either, because they can’t be retrieved in closures. There are ways to solve these problems with conventions, but they require you to mentally “calculate” inputs and outputs, which goes against the straight-forward style of React.

Passing values between Hooks is the core of our proposal, the Render Props pattern is the first thing you would think of without Hooks, but a library like Component Component is not going to work for every scenario you encounter, It has a lot of syntax interference due to “wrong hierarchy”. Hooks use flat hierarchies to pass values — and function calls are the easiest way to pass values.

Bug #8: Cumbersome steps

There are many proposals that fall into this category. They want React to be free of Hooks as much as possible, and most of the way they do it is by having this have Hooks built in, making them extra arguments everywhere in React, and so on.

I think Sebastian’s answer is more convincing of this approach than my description, and I suggest you take a look at the injection model.

I’ll just say that it’s the same reason programmers tend to use try/catch methods to catch bad code, and we prefer the ES module import(or CommonJS require) to the “show” statement AMD passes into require itself.

// Who misses AMD?
define(['require'.'dependency1'.'dependency2'].function (require) {
  var dependency1 = require('dependency1'),
  var dependency2 = require('dependency2');
  return function () {};
});
Copy the code

Yes, AMD may have been more “honest” in stating that modules don’t load synchronously in the browser environment, but when you know that, writing define sandwiches is pointless.

The Try /catch, require, and React Context apis are all real examples of how we prefer the “contextual” experience to direct declarations (even though we generally prefer straight-forward style), and I think Hooks fall into that category as well.

This is similar to grabbing a Component from React when we declare a Component. Perhaps our code would be more decoupled if we exported each component in a factory fashion:

function createModal(React) {
  return class Modal extends React.Component {
    // ...
  };
}
Copy the code

But in practice, this turns out to be tedious. When we really want to catch React in some way, we should do it at the module system level.

This also applies to Hooks. However, as Sebastian mentioned in his answer, it is technically possible to import Hooks of different implementations “directly” from React. (I mentioned this in an earlier post.)

Another way to forcibly complicate ideas is to put Hooks monadic or add class concepts like react.createhook (). Any solution that adds nesting other than the Runtime loses the benefit of normal functions: ease of debugging.

During debugging, ordinary functions do not include any library code, and the flow of values within the component is clear, which is difficult to do indirectly. Solutions like those inspired by higher-order components (” decorator “Hooks) or render props(Adopt proposals or yield for generators, etc.) have this problem. Indirection also complicates static typing.


As I mentioned earlier, this article will not be exhaustive, and there are many interesting issues in other proposals, some of which are more obscure (e.g. related to concurrency and advanced compilation) and may be the subject of another article in the future.

Hooks are not perfect, but they are the best trade-offs we can find to solve these problems. There are some things we still need to fix, which are more awkward in Hooks than in class, and will be in other articles as well.

Whether OR not I’ve covered your preferred alternatives, I hope this article has helped illuminate our thought process and the criteria we consider when choosing an API. As you can see, many things (such as making sure to copy and paste, moving code, adding and deleting dependencies the way you want) have to be optimized for change. I hope the React developers will appreciate the decisions we’ve made.

Why Do React Hooks Rely on Call Order? (2018-12-13)