Skip to main content

Hook Syntax

Hook Syntax is first-class syntax and typechecking support for React hooks, bringing hooks into the React language as their own entities that are syntactically and semantically distinct from regular functions, and using Flow to enforce that the Rules of React aren’t violated.

Basic Usage

The primary difference between writing a function and a hook is the hook keyword:

1import {useState, useEffect} from 'react';2
3hook useOnlineStatus(initial: boolean): boolean {4  const [isOnline, setIsOnline] = useState(initial);5  useEffect(() => {6    // ...7  }, []);8  return isOnline;9}

Hooks can be called just like regular functions:

1import * as React from 'react';2
3hook useOnlineStatus(): boolean {4    return true;5}6
7component StatusBar() {8  const isOnline = useOnlineStatus();9  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;10}

Hooks can be exported just like normal functions:

1export hook useNamedExportedHook(): boolean {2    return true;3}4
5export default hook useDefaultExportedHook(): boolean {6    return true;7}

Hook Type Annotations

There are a few cases where you might wish to define a value as having the type of a hook. Because function types and hook types aren’t compatible (more on this below!), we also introduce a new syntax for hook type annotations, which is simply the existing function type annotation but preceded by hook.

export const useGKOnlineStatus: hook (boolean) => boolean = 
experiment('show_online_status')
? useOnlineStatus
: useAlwaysOnlineStatus

Enforcing the Rules of React with Hook Syntax

With hook syntax, we can now unambiguously distinguish syntactically between hooks and non-hooks. Flow will use this information to enforce a number of the rules of hooks and Rules of React generally.

Preventing Unsafe Mutation

According to the Rules of React, refs aren’t allowed to be read from or written to while a component is rendering, and the return value of other hooks (especially `useState``) cannot be safely mutated directly at all. By making Flow aware of hooks as a first-class concept, we can now detect these issues in many cases and raise errors early, rather than depending on testing to uncover them.

1import {useState, useEffect, useRef} from 'react';2import * as React from 'react';3
4component MyComponent() { 5  const ref = useRef<?number>(null);6  const [state, setState] = useState<{ val: number }>({val: 0});7
8  state.val = 42; // Flow error: cannot mutate return value of hook
9 10 return (11 <div>12 {ref.current /* Flow error: cannot read ref during rendering */}
13 </div>14 );15}
8:9-8:11: Cannot assign `42` to `state.val` because property `val` is not writable. The return value of a React hook [1] cannot be written to. [react-rule-hook-mutation]
12:8-12:10: Cannot read `current` from `ref` [1] because `ref` values may not be read during render. (https://react.dev/reference/react/useRef). [react-rule-unsafe-ref]

Flow currently prevents component props from being modified within the component. Hook syntax allows us to extend this checking to hooks, and will let us detect and raise errors when illegal mutations occur within hook declarations.

1hook useIllegalMutation(values: Array<number>) {2  values[0] = 42; // Flow error: mutating argument to hook
3 // ...4}
2:3-2:11: Cannot assign `42` to `values[0]` because read-only arrays cannot be written to. React hook arguments [1] and their nested elements cannot be written to. [react-rule-unsafe-mutation]

Preventing Conditional Hook Calls

The Rules of Hooks prohibit hooks from being called conditionally. This is covered by React's ESLint plugin, but now Flow will check for these violations too.

1hook useOnlineStatus(): boolean {2    return true;3}4
5component StatusBar(shouldShowOnlineStatus: boolean) {6  if (shouldShowOnlineStatus) {7    const onlineStatus = useOnlineStatus();
8 }9 10 return null;11}
7:26-7:42: Cannot call hook [1] because React hooks cannot be called in conditional contexts. [react-rule-hook]

Preventing Conflation of Hooks and Functions

The distinction between hooks and regular functions is reflected in the Flow type system. Because of the different properties that hooks and functions must obey, it’s Flow error to pass a value defined as a hook into a position that expects a function type, and an error to pass a regular JavaScript function into a position that expects a hook.

1import {useState, useEffect} from 'react';2
3hook useMultiplier(x: number): number {4  const [y, setY] = useState(1);5  useEffect(() => { setY(0) })6  return x * y;7}8
9component Mapper(args: Array<number>) {10  const multArgs = args.map(useMultiplier);
11 12 return multArgs;13}
10:29-10:41: Cannot call `args.map` because function [1] is a React hook but function type [2] is not a hook in the first argument. React component properties [3] and their nested props and elements cannot be written to. React hooks and other functions are not compatible with each other, because hooks cannot be called conditionally. [react-rule-hook-incompatible]

In addition, Flow enforces that callees with hook-like names inside hooks and components are indeed hooks. We also ensure that callees inside of regular function definitions are never hooks.

1hook useHook() { return null }2
3function regularJavascript() {4  const x = useHook(); // Flow error: cannot call a hook outside of a component or hook
5}6 7component Component() { 8 const renamedHook = useHook;9 renamedHook(); // Flow error: cannot call a hook whose name does not begin with `use`
10 11 return null;12}
4:13-4:21: Cannot call hook [1] because React hooks can only be called within components or hooks. [react-rule-hook]
9:3-9:15: Cannot call hook because callee [1]'s name does not conform to React hook rules. Hook names must begin with `use` followed by a capitalized letter. [react-rule-hook]