React Hooks

(although they're now available with other frameworks)

What

  • A new method of adding functionality to a function component
  • That includes state, which previously required a class
  • and lifecycle methods (not all)
  • You can make custom hooks, which LOTS of people have, to do common tasks with to enhance your component

Why

  • Hooks are about sharing functionality (inc. state) between components
    • That's why, by convention, their names begin with use (useState, useEffect, useDebounce, etc)
  • Previously only class-based components had the ability to hold state, so if you were moving state up and down the component hierarchy you'd have to keep re-writing your component from function to class and vice versa
    • They mostly do away with the need for classes

How do they work?

  • Short answer: closures
  • Long answer: here

OKAY, LET'S.... NO!

First read the Rules of Hooks

  1. Only Call Hooks at the Top Level

    • Don’t call Hooks inside loops, conditions, or nested functions (because they work with closures/scope)
  2. Only Call Hooks from React Functions

    • Don’t call Hooks from regular JavaScript functions. Instead, you can:
      • Call Hooks from React function components.
      • Call Hooks from custom Hooks

OK, now let's go...

Anatomy of a hook

  • It's just a function itself!
  • They vary, but commonly you do:
const something[s]ToHelpYouUseIt? = hookFunction(arguments);

When you call the hookFunction you pass it [generally 'starting] arguments'/defaults it needs to setup

Standard Hooks

You get them all from the react package:

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

Where do I get them?

useState

Replaces state we get from classes

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

You use it like so:

So, from our Anatomy of hooks...

const something[s]ToHelpYouUseIt? = hookFunction(arguments);
  1. 0 is the initial value the first time the hook runs
    • You can also pass a function for 'lazy evaluation'
  2. Calling useState returns an array containing:
    1. A value you are storing/observing
    2. A setter function, which can change it
      • Setter can take a new value as an argument
      • or it can take a function that receives the last known state value as an argument to it

useState Example

useEffect

Replaces componentDidMount & componentDidUpdate [& componentWillUnmount]

useEffect(/* Callback */, [/* dependancies */]

You use it like so:

So practically

useEffect(
  // Callback
  () => {
    // Do something on mounting/updating
    const handler = setTimeout(() => {
    // just using a timeont as an example because it needs cleanup
    }, delay);

    // [optionally] Clean up when done (componentWillUnmount)
    return () => {
      clearTimeout(handler);
    };
  },
  // Dependancies:
  [value, delay] // Only re-run effect if these values change
);

The Dependencies

  • No deps array passed = renders every time
  • Empty array = runs on initial render
  • Array with deps = ONLY when those deps change

 

There is a rule in the eslint package that will help you (react-hooks/exhaustive-deps)

useEffect Deps Example

useEffect: Ajax Abort Example

useContext

A nicer way of using the context API

Less Common Standard Hooks

Less Common Hooks

useReducer

  • useReducer is like a compound useState
  • It's for complex state
    • objects, not primatives
    • business logic that gets shared
  • It can change several aspects of the state at once for minimum re-renders (demo)
  • takes complexity out of component
  • Look at the end of this article for some comparison

useReducer

  • reducer functions are:
    • pure functions (same input, same output. Always. And no side effects!)
    • not a react thing (they're a general programming concept)
      • they are made famous in Redux
    • management software for a store
  • A store is somewhere we put state
  • To change store data, you 'send it a signal' (dispatch an action)
    • dispatch is a function. It lets you send those actions to a store
  • Actions have
    • a type: Usually a string, like 'ADD_ITEM' (so the reducer function can look up what to do)
    • [optional] data: which is needed to complete the action (such as new data for the item

useReducer (demo)

  • the function takes in an action and uses it to create an updated copy of the state, which it then uses to update state by wholey replacing what is there
    • we do not 'mutate state'
const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useCallback (demo)

  • Changes in dependancies cause functional components to re-run
  • If you define a function in one of these components then you are re-creating a new function every time
  • useCallback keeps a copy of the function elsewhere, so that it is not remade every time.
  • Like useEffect, you can pass a second argument which is a deps array. If the deps change then the function is re-made.
  • See 'memoization' in function presentation
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useMemo

  • Like useCallback, but for remembering values, not functions
  • It is great for remembering the result of big computations
  • You pass it a function which returns that value
  • Also useful for memoising the value you pass to a context
  • N.B. TECHNICALLY you cannot rely on it not re-calculating 
    • Make sure your app works without it. It is a 'nice-to-have' tool
  • demo
const memoizedValue = useMemo(
  () => computeExpensiveValue(a, b), 
  [a, b]
);

useRef (demo)

  • A fast way of using refs
  • Similar to createRef BUT refs are persisted between renders
function TextInputWithFocusButton() {
  const inputEl = useRef();
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useImperativeHandle (demo)

  • Used with forwardRef
  • useImperativeHandle takes a ref object and a createHandle function whose return value “replaces” the stored value in the ref object.
  • Also [optionally] takes a deps array
import { useRef, useImperativeHandle, forwardRef}

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

export default FancyInput

useLayoutEffect (demo)

  • Like useEffect BUT
  • it fires synchronously after all DOM mutations (and pre-paint)
  • use to read the DOM's layout
  • use to adjust DOM before paint
  • N.B. Prefer the normal useEffect to avoid performance deterioration due to render-blocking
  // useEffect(() => {
  useLayoutEffect(() => {
    listRef.current.scrollTop = listRef.current.scrollHeight;
  });

useDebugValue

  • Allows you to write labels for custom hooks that can be shown in the react dev tools
  • You will 99% never use
  • Only use in your own custom hooks
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

useId

  • Generates IDs
  • DON'T USE FOR LIST KEYS!!
function NameFields() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id + '-firstName'}>First Name</label>
      <div>
        <input id={id + '-firstName'} type="text" />
      </div>
      <label htmlFor={id + '-lastName'}>Last Name</label>
      <div>
        <input id={id + '-lastName'} type="text" />
      </div>
    </div>
  );
}

useDeferredValue

  • New in React 18!
  • Allows concurrent work
  • Use the useDeferredValue hook returns a value
  • it's input is a value derived from another
  • the hook means that re-calculating this value will be deferred while more urgent updates are being processed
  • demo
function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // Memoizing tells React to only re-render when deferredQuery changes,
  // not when query changes.
  const suggestions = useMemo(() =>
    <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">
        {suggestions}
      </Suspense>
    </>
  );
}

useTransition

  • New in React 18!
  • Allows concurrent work
  • Use the useTransition hook to indicate work of a lower importance
  • demo
  const [searchTerm, setSearchTerm] = useState("");
  const [data, setData] = useState(USERS);
  const [isPending, startTransition] = useTransition();

  function handleChange(value) {
    setSearchTerm(value); // more important
    startTransition(() => { // less important
      setData(filterUsers(value));
    });
  }

return (
  <>
    <input value={searchTerm} onChange={handleChange} />
    {isPending ? <p>Updating list</p> : null}
    <PeopleList people={data} />
  </>
)

Custom Hooks

What is it?

  • You can make your own hooks to split re-usable functionality out of a component
  • The are defined because they themselves contain use react hooks to achieve their aims (and therefore generate state updates)
  • demo

Useful for common tasks

Custom Hook Examples