What did I learn from the workshop Basic React Hooks by Kent C. Dodds?

Previously, I wrote down my learnings from the React Fundamentals workshop.

The second workshop introduces React hooks.

Here are some insights that I found useful:

Set Initial State Via Props

Set an initial value by adding props to a component: <Greeting initialName="George">

Take that as an argument to the function and pass it down to useState:

function Greeting({ initialName = '' }) {
  cont[(name, setName)] = React.useState(initialValue)
}

// more code

Make then onChange handler a controlled input: <input onChange={handleChange} id="name" value={name} />

You can also use lazy initialization with a function. If you pass a function as the initial value to the useState hook, React will call it during first render. Thus you can avoid re-creating the initial state.

(In fact, that’s how the ReasonReact useState hook works, see here).

The React useEffect Dependency Array Does Shallow Comparison

You can watch a lesson on egghead.io on how to use the lodash library and useRef for deep comparison (paid content)).

Or a blog post on dev.to.

Derived State

During one of the exercises, I realized the benefits of deriving state. I may have used it before, but the tic-tac-toe game in the workshop drove it home.

The exercise has an array of squares as the game board ([squares, setSquares] = React.useState(Array(9).fill(null)).

I need to handle other statuses like winner, nextValue. My natural inclination was to write more useState hooks for these states.

Instead, it’s more prudent to create functions that calculate these pieces from the squares state. Every time squares changes, the functions will fire off because of React’s re-render. And you will get updated (derived) state.

function Board() {
  const [squares, setSquares] = React.useState(Array(9).fill(null))
  const nextValue = calculateNextValue(squares)
  const winner = calculateWinner(squares)
  const status = calculateStatus(winner, squares, nextValue)
  function selectSquare(square) {
    if (winner || squares[square]) {
      return
    }
    const squaresCopy = [...squares]
    squaresCopy[square] = nextValue
    setSquares(squaresCopy)
  }
  function selectTwoSquares(square1, square2) {
    if (winner || squares[square1] || squares[square2]) {
      return
    }
    const squaresCopy = [...squares]
    squaresCopy[square1] = nextValue
    squaresCopy[square2] = nextValue
    setSquares(squaresCopy)
  }
  // return beautiful JSX
}
above code is from Kent C. Dodd's blog

DOM Interaction with useRef

React consists of two parts: React.createElement (or its syntactic sugar JSX) and ReactDOM.render.

The first one (part of the react package) creates React elements and has nothing to do with the browser’s DOM.

ReactDOM.render is from the react-dom package (not the react package). The react-dom library “provides DOM-specific methods”.

For accessing DOM nodes directly, there’s a hook called useRef. A ref is a reference to a DOM element.

Error Boundaries Can Be Useful

Since React Hooks landed, I have not used class components in new projects.

Error Boundaries are one of the APIs that still work as a class component, and cannot be modeled via Hooks.

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

Luckily, the react-error-boundary library exposes a more user-friendly API. You don’t have to write a class-based component (the ugliness….).

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{ color: 'red' }}>{error.message}</pre>
    </div>
  )
}
//

function App() {
  return (
    <div>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Greeting />
        <Farewell />
      </ErrorBoundary>
    </div>
  )
}
above code is from Kent C. Dodd's blog

react-error-boundary also gives you a useErrorHandler custom hook to handle other errors, which cannot be handled with React’s own Error Boundaries:

  • Event handlers (learn more)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server side rendering
  • Errors thrown in the error boundary itself (rather than its children)

For example, you can do this:

function Greeting() {
  const [greeting, setGreeting] = React.useState(null)
  const handleError = useErrorHandler()

  function handleSubmit(event) {
    event.preventDefault()
    const name = event.target.elements.name.value
    fetchGreeting(name).then(
      newGreeting => setGreeting(newGreeting),
      handleError,
    )
  }

  return greeting ? (
    <div>{greeting}</div>
  ) : (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit"}>
        get a greeting
      </button>
    </form>
  )
}
above code is from the react-error-boundary documentation

Further Reading