Hooked
Every June, we like to grab a bowl of popcorn and tune in to watch Apple’s WWDC Keynote, where Apple reveals what new software toolkits and hardware devices developers have to look forward to in the coming year. While we like to keep an eye out on what’s happening in the Apple developer space, mostly we just watch it to have a laugh at the peak Silicon Valley pomp and circumstance.
At Formidable, the tools we use to build web and mobile applications are open source, and there is no one company that we rely on for new releases. The closest thing to WWDC in the Formidable ecosystem is React Conf where the React core team at Facebook has historically presented what they’ve been working on in secrecy. And this morning’s React Conf keynote was no exception.
We're very excited about what we learned and we wanted to share our take on React Hooks, a new React API that gives React functional components the ability to use state and controlled side effects without the need for any additional helper libraries.
Say what now?
A future version of React (likely 16.7) will ship with a set of special functions called Hooks. When you call one of these functions within a React function component, it indicates to React that the component has some behavior other than simply rendering a set of elements based on its props. For instance, the useState
hook tells React that a component... well, uses state, and the useEffect
hook indicates that the component causes some side effect in the wider application. For its part, React exposes APIs for interacting with these behaviors in the return value of these "hook functions". An example might help here:
import { useState, useEffect } from 'react'; function Example() { // Every time you call setCount, the component re-renders const [count, setCount] = useState(0); // Whenever the component re-renders, after React finishes rendering, // we update the document title with the value from our state useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); }
This example only scratches the surface of the Hooks API—we could go into far more detail, but the API documentation is incredibly thorough and does a better job of explaining its capabilities than we could in such a short time. For now, we're going to focus on our first impressions.
First Impressions
“Whoa. Sweet!” -Jani, probably
You can call a method in a component function and suddenly that function becomes reactive to updates caused by changes in internal or external state? Rad!
In many ways Hooks are the most “React-esque” feature we’ve seen in several years—they have that trademark React quality of being simple to use, but extremely clever underneath. Simply by calling a method we can make a function component reactive to changes in internal or external state? Sweet.
On its face, the Hooks API feels more ergonomic and practical than other common approaches to lifecycle management—even those we’re big fans of like recompose, reselect, and Redux. It lets us avoid massive trees of wrapper components, bundle-bloating third-party libraries, and confusing generator stack traces while debugging, while getting most of the same benefits.
Bring Your Own Hooks
Above we mentioned just two hooks: useState
and useEffect
. In fact, there are several others, such as useContext
(which exposes an interface to the Context API), and useReducer
(which lets you manage component state with Redux-style reducers.)
Considered on their own these are all well and good, but what really excites us is the ability to create what React calls “Custom Hooks”—functions that “package together” invocations of several related hooks for use as an atomic unit elsewhere. For instance, you might combine the useState
and useEffect
hooks to create your own useAPI
hook, which fetches, stores, and manipulates data from an API—all while providing a single, clean interface to users.
This looks like a clean abstraction for modeling shared functionality in your application, and the the possibilities really explode once you realize that we’ll be able to publish these hooks as open source libraries! Unlike previous iterations of shareable component behaviors, custom hooks are easy to use and easy to compose: just call useMyHook
in a component. It seems likely that hooks could solve the problems that mixins, higher-order components, and “render props” tried to solve, but never could because of their lack of composability, ergonomics, or both.
How does it actually work?
While reading the code sample above, you may have noticed something...odd. We never actually defined a state variable anywhere—we simply used it as if it had always existed. What gives?
Behind the scenes, React “knows” that it should associate a given call to Hook functions with the React component it’s rendering, and the order in which those hook functions are called lets React “remember” which hooks are which across several render passes. (One way to visualize this is by imagining each call to useState
creating a new “memory cell” to store the actual underlying value.)
Tradeoffs
First and foremost, hooks can only be used within function components, which creates some amount of friction if you need to share behavior between custom hooks and class components.
Secondly, because React relies on hooks being called in a consistent order on every render pass, there are strict limitations on how they must be called within a function component—namely, the same hooks must be called in the same order, on every render pass, every time. This means that calling hooks within (for instance) conditionals, loops, or try-catch blocks is an explicit misuse of the feature.
Although this rule is simple to follow, React has no way to prevent it without the use of out-of-band techniques (although, to its credit, the Hooks API ships with an ESLint plugin to help you remember). Nevertheless, this is an additional, subtle detail to remember, which has the potential to trip up beginners and experts alike, and we can’t help but imagine that bugs related to this behavior are going to be tricky to chase down.
Are function components really functional?
Until now React’s function components have served as a clear complement to class components—stateless and referentially transparent, where class components were typically stateful and side-effectful. However, with the introduction of hooks (and Suspense), the line blurs—we can no longer identify “stateless, functional components” at a glance. Now it’s necessary to understand the behaviors associated with the hook (or hooks, in the plural) a component uses, and especially the conditions under which those behaviors may cause the component to re-render.
But, with that said...does it really matter? After all, functional programming doesn’t necessarily dictate that functions must be completely side-effect free, but rather that any side-effects must be managed in a consistent way, which is exactly what the Hooks API does.
Because React fully controls when your function is called it knows how to associate an invocation of hook functions with its invocation of your “component function”, and defines a strict set of semantics for doing so. Because hooks require us to declaratively define our data dependencies and side effects, they’re still easy to reason about, and even easier for static code analyzers like Flow or TypeScript to validate.
With this in mind, hooks feel like a pragmatic solution to a tricky problem, but it remains to be seen if this design decision is worth the departure from the widespread, existing mental model.
Summary
We’re excited about the possibilities the new React Hooks API offers, and we’re looking forward to experimenting with them in the coming days and weeks! We expect that (along with the community at large) we’ll uncover answers to the questions raised here, and we can’t wait to see the new patterns and solutions that shake out.
All things considered, this feels like a huge step in the direction of a simpler, cleaner API for React components. Although class-based components will continue to be supported for the time being, we have the distinct impression that the community will embrace “Hooked-up” function components, because let’s face it—writing apps is a lot more fun without all the syntax and ceremony!