BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News React 16.8 Releases React Hooks: Reusable and Composable Logic in React Components

React 16.8 Releases React Hooks: Reusable and Composable Logic in React Components

This item in japanese

The React team recently released React 16.8 featuring React Hooks. Hooks encapsulate impure logic (such as state, or effects) with a functional syntax that allow hooks to be reused, composed, and tested independently. Developers may additionally define their own Hooks by composition with the predefined Hooks shipped with React 16.8. Hook-based React components allow developers to construct complex React component trees which are shorter and easier to understand.

While React Hooks has been used extensively at Facebook, the feature, which comes with caveats, remains an issue of sometimes heated discussion among some developers. Alternative community-led proposals favoring a purely functional approach do not so far replicate a key benefit of React Hooks: enabling Concurrent Mode for the upcoming React release.

A significant portion of the React components constituting a React application are realizing effects, and interacting with local and global state. Different components often perform similar effectful computations. React Hooks packages those effectful computations within a functional syntax so they can be reused in the frame of a React application. The React documentation provides the example of a component FriendStatus from a chat application that displays a message indicating whether a friend is online or offline:

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

FriendStatus uses the pre-defined useState and useEffect to implement its specified behaviour by means of a JavaScript function. useState exposes a setter-getter API to access the local state that it creates. useEffect runs an effect on each render of FriendStatus. That effect is specified through a function which runs the effect, potentially initializing any relevant resources, and returning a clean-up function potentially freeing no longer needed resources.

In a React component tree, the specified behaviour will appear as a single <FriendStatus> node. With the usual class-based implementation of the behaviour, reusing state or effect logic through higher order components would result in those higher-order components potentially polluting the component tree, impacting negatively readability. On the other hand, not reusing state or effect logic is prone to the category of bugs linked to manual duplication, potentially leads to larger code size, and the corresponding sub-optimal user experience.

React 16.8 ships with 10 pre-defined hooks addressing specific effectful concerns. Those hooks are closely integrated to React, and would not hold any meaning outside of a React-like context and runtime.

Pre-defined React Hooks can be composed into Custom Hooks, which are functions defined by developers and whose implementation calls on pre-defined hooks. The React documentation provides the following example of a chat message recipient picker ChatRecipientPicker that displays whether the currently selected friend is online:

import React, { useState, useEffect } from 'react';

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

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>
    </>
  );
}

The ChatRecipientPicker React component reuses the useState hook, and the useFriendStatus custom hook. The previous examples showcases a key advantage of React Hooks which is the seemingly natural composition of hooks: the local state capturing the recipient ID can be passed as parameter to useFriendStatus, providing the expected behaviour of computing whether the currently selected recipient is online, independently of when the recipient changes.

While React Hooks composition may look similar in syntax to regular JavaScript function composition, the two counter-intuitively differ in semantics, and Hooks are subject to specific rules for their good behavior:

The rules governing the Hooks compositional behaviour, which may be enforced by a dedicated eslint plugin, have been a challenge and a source of confusion for some developers. A developer explains:

I’m not used to needing a linter to tell me when valid JavaScript isn’t valid because I happen to be using a particular library. In my opinion (and nothing more than that) this goes against some very fundamental principles about programming

Other developers enthusiastically received Hooks and are building their own intuition and recommended practices.

As a possible remedy, React published a detailed explanation linking the compositional behaviour to details of React Hooks’ implementation. The necessity however to introduce implementation details to understand semantics and build a mental model may be a hurdle towards larger and faster acceptation by developers.

React Hooks is new, and as bugs and inconsistencies related to the integration of Hooks into React rendering pipeline appear, common pitfalls and countering strategies are actively being worked out by the React team.

The React team promotes a gradual adoption strategy and discourages rewriting existing class-based code base with Hooks. The documentation comments:

You don’t have to learn Hooks right now. Hooks have no breaking changes, and we have no plans to remove classes from React. (…) We don’t recommend rewriting your existing applications to use Hooks overnight. Instead, try using Hooks in some of the new components, and let us know what you think. Code using Hooks will work side by side with existing code using classes.

Alternative proposals with better properties, or favouring a purely functional approach are still being investigated by some React developers. However, none of them so far replicates a key benefit of React Hooks which is to enable the upcoming React release with Concurrent Rendering. Dan Abramov, co-author of Redux and Create React App, elaborates:

We want closures to capture the values we rendered with, and to keep seeing those values forever. That’s really important for concurrent mode where a notion of current value doesn’t really exist. Hooks design models a component as being in many non-clashing states at the same time, instead of switching the current state (which is what classes model well). People don’t really need to think about these details, but they’re motivating the design a lot.

Rate this Article

Adoption
Style

BT