Error Boundaries

React: Components

A brief overview of react and its component architecture

Functional Programming

React was heavily inspired by functional programming.

The laws of functional programming:

  • In functional programming, functions can't have side effects.
  • A function should work like a black box.
  • Input is given in a predictable way.
  • A function can only rely on its inputs to do operations (no global variables).
  • A function should not modify the input, as it is immutable.
  • A function cannot modify any outside variable.

Components

Component architecture is very popular in the web world.

  • A component is a small, reusable chunk of ui.
  • A component encapsulates a single responsibility.
  • In the ideal world, logic should be separated from a component as much as possible.
  • The separation gave rise to the idea that there are smart components, & dumb components

Smart

A component that contains some logic, usually in charge of a CRUD operation on some level.

Dumb

The presentation layer. No logic allowed, only pure, stateless components.

The problem: Too many components

Breaking apart logic into separate components means you often times have to double the amount of components you need to build.

 

Component trees can get messy and hard to manage.

 

Often, logic is bundled together inside one component. Meaning your components no longer follow the single responsibility principle.

What can we do? (a.k.a, what are hooks?)

  • On the surface, hooks allow functional components to access a class components lifecycle methods.
  • Functional components can now have state.
  • Logic again can once be brought to functional (display) components.

Logic is no longer separate?

Logic can actually be separated much easier with hooks:

// Logic
export default function useInput(value) {
  const [state, setState] = useState(value)

  const handleInputChange = ({ target: { value } }) => {
    setState(value)
  }

  return [state, handleInputChange, setState]
}

// Presentation
function InputComponent() {
  const [state, handleOnChange] = useInput('')
  return (
    <Input
      onChange={handleOnChange}
      value={state}
    /> 
  )
}

Logic is no longer separate? (p2)

function CandyTable({ flavorID }) {
  // Logic
  const [{ fetching, data }] = useCandyQuery(getFlavorQuery(flavorID))

  // Display
  if (fetching) {
    return (<Loading />)
  }
  
  return (
    <Card small className="mb-4">
      <table className="table mb-0">
        <thead className="bg-light styled-header">
          <tr>
            <th scope="col" className="border-0">
              Candy Name
            </th>
            <th scope="col" className="border-0">
              Flavor
            </th>
          </tr>
        </thead>
        <tbody>
          {
            data.map((item) => {
              return (
                <CandyItem
                  key={`${item.id}-candy-item`}
                  {...item}
                />
              )
            })
          }
        </tbody>
      </table>   
    </Card>
  );
}

In summation

  • Functional programming is clean(er)
  • Smart Components help
  • Hooks help bestist

Error handling

There are three (main) kinds of errors 
(in javascript)
  1. System To System (API)
  2. Code Failure (syntax/types)
  3. Invalid State

How do we minimize errors?

  1. API
    • Retrys
    • Caching of requests
    • Make smaller requests for more specific data
  2. Code
    • Linting
    • Avoid code smell
    • Types
  3. Invalid state
    • functional programming
    • ???

How to recover from errors?

  1. API
    • Retry disconnects
    • Allow user to fire action again
  2. Code
    • Restart
    • ​???
  3. Invalid state
    • Restart
    • ???

Error Boundaries

Finally!!

The Air Boundary

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

What Are Error Boundaries?

A JavaScript error in a part of the UI shouldn’t break the whole app.

Error boundaries do:

Catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.

Catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

What Are Error Boundaries not?

Error boundaries do not catch errors for:

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

Error boundaries are not wrappers for every component

Where to Place Error Boundaries?

Error boundaries do not catch errors for:

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

Error boundaries are not wrappers for every component

Conceptual Demo

Error Boundaries

Best Practices 

Bubble bubble (top level)

  • ErrorBoundary
    • ChatContext
      • ChatWindow
      • MessageContainer
        • BuggyChatComponent

While this error boundary catches the buggy chat component, it unmount the entire children tree.

Down With The Sickness (bottom level)

  • ErrorBoundary

    • ChatContext

      • ChatWindow

        • ErrorBoundary

          • ChatMessage

      • ​MessageContainer

        • ErrorBoundary

          • BuggyChatComponent

This error boundary catches the BuggyChatComponent,

but requires that almost every component be wrapped.

Goldilocks (medium level)

  • ErrorBoundary

    • ChatContext

      • ErrorBoundary

        • ChatWindow

          • ChatMessage

          • ChatMessage

      • ErrorBoundary

        • ​MessageContainer

          • BuggyChatComponent

Best Practices

An Error boundary works many levels deep, so there’s no need to “wrap at every level”. Just put your <ErrorBoundary> in a few strategic places.

- Dan

  • Craft messages that are easy to understand and which are actionable

  • Log pertinent application state and recent user activity

  • Automate recovery wherever possible

  • Allow the user to retry actions

  • Sometimes, a fallback component isn't the right answer.

Best Practices (cont)

Questions:

Do we want to automate github ticket creation for our production environment?

Made with Slides.com