iJS CONFERENCE Blog

React Hooks: React More Functional Than Ever

Sep 11, 2019

For a long time, it has been possible to create React components on the basis of classes or functions. With the newest version, functional approaches are clearly gaining more traction.

As you may know, I really like functional programming techniques. Beyond all logical and technical considerations, I favor this style of programming since it is a great experience to build new things from simple functions in a reliable, testable and consistent way.

The continuing evolution of mainstream programming methodologies is not news anymore, we have all been watching this happen for years now. Individual developments can still be worth mentioning, and I was positively surprised a while ago to read about new features and plans for React. With latest developments, React is clearly moving in the direction of functional ideas, much more clearly than it did in the past.

As a first example I should mention the function memo, which became available in React 16.6. The name itself reminds of the functional world: “memoization” is the name of a technique often used by functional programming languages to save return values for later calls. Imagine you have this simple function:

const calc = x => {
return ...; // some complicated calculation!
}

A frequently quoted advantage of functional programming is the fact that functions are stateless, “pure” being the technical term for this. They are independent of influences “from the outside”, they avoid so-called side effects. The simple function calc depends on its input parameter x alone – every time the same value is passed for x, the function renders the same result. A “pure” function like this can be memoized, for instance with this small helper function:

const memo = f => {
  const results = {};

  // For demonstration purposes, f is limited to one parameter!
  return x => {
    if (results[x]) {
      return results[x];
    } else {
      result = f(x);
      results[x] = result;
      return result;
    }
  };
};

const memoizedCalc = memo(calc);

// With this first call, calc performs its calculation
console.log(calc(10));

// For further calls with the same parameter value, the
// stored return value is delivered directly, the calculation
// is not executed again.
console.log(calc(10));

Of course the sample implementation is just an illustration. In reality you don’t need to write such helpers yourself, since existing libraries like Lodash or Ramda already include much better implementations that also deal with additional parameters and other complex cases.

React components include a function that is called to render, i.e. to display, the visual element represented by a component. When you implement a component as a class, you code this logic in the method render. Even in the context of classes there are some expectations defined for this method (as described in the documentation here). For instance, you should not change component state from this method. This recalls the functional idea: the method render depends on state and props of the component, but it shouldn’t trigger any side effects. If you adhere to these guidelines correctly, you can now utilize React mechanisms to ensure that render won’t be called more often than necessary. Traditionally you implement the method shouldComponentUpdate for this purpose, with any logic to decide whether rendering is required or not. In many cases you can also use PureComponent as a base class, which renders visual updates only if state or props have changed.

Before version 16.6, React didn’t have any similar built-in functionality to influence rendering decisions in functional components. If you have followed my examples, you will understand the purpose of the function memo now: it memoizes React components.

const memoizedComponent = React.memo(props => <div>...</div>);

This is no black magic, but it immediately impresses with its brevity and conciseness! The shortest equivalent class-based component would likely derive from PureComponent:

class CleverComponent extends PureComponent {
  render() {
    return <div>...</div>;
  }
}

This code is not just longer and more verbose than the functional variation, it is also somewhat harder to understand. Perhaps it was the author’s intention to achieve the same efficient rendering the functional sample shows – but a reader of the code would only understand this if they know the class PureComponent well and remember how it differs from the base class Component. In comparison, the intention is immediately obvious when a function called memo is used, or at the very least a quick Google search will find an explanation easily.

Shiny and New: Hooks

Since version 16.8, React has received updates to include Hooks. This functionality puts functional components on a level with class-based ones, making the more concise functional syntax a seriously appealing contender.

Before this point, the concept of higher order componentswas used to “extend” a React component using functional approaches. The term is derived from the higher order function, and a higher order componentis nothing more than a function which encapsulates an existing component. To illustrate using a line from the React docs:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

This approach was widely used to interface React components with additional
systems or frameworks. The function connect from the Redux library is a good
example: to make a Redux store available for an independent component, you call
connect as a higher order component:

const reduxConnectedComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

The library recompose exists – though it is now deprecated – as a collection of flexible higher order componentsto support everything class-based React components can do. This includes working with state, reacting to props changes and lots more. I built complex components with help from recompose, but a complete sample would be too large to show here. The general structure of such a component might look a bit like this:

const Debounce = compose(
  onlyUpdateForPropTypes,
  setPropTypes({
    // ...
  }),
  defaultProps({
    // ...
  }),
  withState('viewValue', 'setViewValue', ({ value }) => value),
  withPropsOnChange(
    ['debounceWait', 'onChange'],
    ({ debounceWait, onChange }) => ({
      onChangeFunc: _.debounce(debounceWait)(onChange)
    })
  ),
  withPropsOnChange(['extract'], ({ extract }) => ({
    extractFunc: // ...
  })),
  withHandlers({
    childChange: props => e => {
      // ...
    }
  }),
  lifecycle({
    componentWillReceiveProps(np) {
      // ...
    }
  })
)(({ children, valueField, changeEvent, viewValue, childChange }) => {
  // render here
});

This is certainly a rather complex example and the syntax may not look intuitive to you. It is possible to get used to this and with some experience I find the syntax quite readable. The outer function compose indicates a common functional approach where multiple functions are “chained” to create a new function. All the functions passed to compose work together, in sequence, to wrap the original rendering logic. That’s onlyUpdateForPropTypes, setPropTypes, defaultProps, withState and so on, eight functions altogether.

Each of these functions is a higher order component, and that’s exactly the problem with this approach. An onion-style wrapping structure is generated, like a deeply nested function call:

onlyUpdateForPropTypes()(
  setPropTypes(...)(
    defaultProps(...)(
      withState(...)(
        withPropsOnChange(...)(
          withHandlers(...) (
            lifecycle(...)(
              // render function here
            )))))))

The resulting components is equally deeply nested, since it is encapsulated multiple times, eight times in the example! The concern is obvious: won’t this approach impact performance? In any case, the debugging experience is not exactly improved in this scenario. React apps always work with impressively deep hierarchies due to their component structure, but if many individual components add loads of nesting layers simply because they use higher order componentslike in the example, the overall application structure gains great complexity.

The Hooksconcept offers a new and different approach to enable a full component feature set for functional components without generating similar nesting depth. At the same time, the syntax in code is also simplified. Here is a short example from the docs that uses state in a functional component:

const Example = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
};

The function useState is the Hook. It generates state for the component and returns two values, the state itself and a function to modify it. The term Hookis meant to hint at the fact that the mechanism hooksinto React. The inner workings of all hooks are not exactly the same, but it is easy to imagine how these functions could participate in lifecycle management mechanisms supplied by React and thereby influence the behavior of the resulting component.

Another example is the HookuseEffect. As explained above, render is not meant to trigger side effects. In class components you use various React lifecycle methods instead, to trigger side effects outside the render context. For instance, the methods componentDidMount and componentDidUpdate are often used to load data. This was previously impossible with simple functional components, but it can be achieved quite easily with useEffect:

const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // This code triggers a side effect asynchronously, but
    // it has access to the render context through its closure.
  });

  return (...);
}

The following listing shows a more complex example of a context provider implementation I wrote. You can see two additional Hooksin action here, useRef and useCallback. Many of the Hookscalled in this code also use dependency lists. For instance, the memoized callback updateTokenInfo is automatically regenerated when any of the three values serviceBaseUrl, username or password change. This is a powerful feature which makes components implemented on the basis of Hookshighly performant.

const ReportServerProvider = ({
  username,
  password,
  serviceBaseUrl,
  children
}) => {
  const [tokenInfo, setTokenInfo] = useState(null);
  const [updateTokenTimeout, setUpdateTokenTimeout] = useState(null);
  const refUpdateTokenTimeout = useRef(updateTokenTimeout);

  const updateTokenInfo = useCallback(() => {
    getToken(serviceBaseUrl, username, password).then(token => {
      setTokenInfo({ token, authHeaders: getAuthHeaders(token) });
      setUpdateTokenTimeout(
        setTimeout(updateTokenInfo, (token.expires_in - 60) * 1000)
      );
    });
  }, [serviceBaseUrl, username, password]);

  useEffect(() => {
    refUpdateTokenTimeout.current = updateTokenTimeout;
  }, [updateTokenTimeout]);

  useEffect(() => {
    updateTokenInfo();
    return () => clearTimeout(refUpdateTokenTimeout.current);
  }, [serviceBaseUrl, username, password, updateTokenInfo]);

  return (
    <ReportServerContext.Provider value={{ tokenInfo, serviceBaseUrl }}>
      {children}
    </ReportServerContext.Provider>
  );
};

At the time of writing, React supports ten different Hooks. The author of the aforementioned library, recompose, was actively involved in the development of this new concept, and he’s convinced that Hookscover all use cases of recomposeby now. Structural issues of higher order componentnesting are much improved by this approach!

To make the simple syntax of Hookspossible, there are some rules you should observe. Current eslint-plugin-reactversions are fully up to date and will warn you if you are using a Hookincorrectly. As a simple example, Hooksmay only be used within functional React components, and only on the top level of the component function. However, the eslintrules are very clever and can even detect when dependency lists should be extended to include additional items. I definitely recommend taking advantage of eslintwhen you start working with Hooks.

Good documentation for Hooksis available online and many third-party projects now offer their functionality in the form of additional Hooksinstead of higher order components. Facebook points out that Hookslive side-by-side with class-based components, so there is no pressing need to re-implement anything. However, they also mention that Facebook’s own apps based on React will favor functional approaches in the future – an impressive step, and as a fan of functional programming I’m happy to hear that! I recommend you spend some time with functional React and Hooksand get to know this new structure in detail.

Sign up for the iJS newsletter and stay tuned to the latest JavaScript news!

 

BEHIND THE TRACKS OF iJS

JavaScript Practices & Tools

DevOps, Testing, Performance, Toolchain & SEO

Angular

Best-Practises with Angular

General Web Development

Broader web development topics

Node.js

All about Node.js

React

From Basic concepts to unidirectional data flows