gatsby
Version: 
Blazing fast modern site generator for React
148 lines (136 loc) • 4.09 kB
JavaScript
import * as React from "react"
import { ErrorBoundary } from "./components/error-boundary"
import { ShadowPortal } from "../shadow-portal"
import { Style } from "./style"
import { BuildError } from "./components/build-error"
import { RuntimeErrors } from "./components/runtime-errors"
import { GraphqlErrors } from "./components/graphql-errors"
import { DevSsrError } from "./components/dev-ssr-error"
import { GetServerDataError } from "./components/getserverdata-error"
const reducer = (state, event) => {
  switch (event.action) {
    case `CLEAR_COMPILE_ERROR`: {
      return { ...state, buildError: null }
    }
    case `CLEAR_RUNTIME_ERRORS`: {
      return { ...state, errors: [] }
    }
    case `CLEAR_DEV_SSR_ERROR`: {
      return { ...state, devSsrError: null }
    }
    case `SHOW_COMPILE_ERROR`: {
      return { ...state, buildError: event.payload }
    }
    case `SHOW_DEV_SSR_ERROR`: {
      return { ...state, devSsrError: event.payload }
    }
    case `HANDLE_RUNTIME_ERROR`:
    case `SHOW_RUNTIME_ERRORS`: {
      return { ...state, errors: state.errors.concat(event.payload) }
    }
    case `SHOW_GETSERVERDATA_ERROR`: {
      return {
        ...state,
        getServerDataError: event.payload,
      }
    }
    case `SHOW_GRAPHQL_ERRORS`: {
      return {
        ...state,
        graphqlErrors: event.payload,
      }
    }
    case `CLEAR_GRAPHQL_ERRORS`: {
      return { ...state, graphqlErrors: [] }
    }
    case `DISMISS`: {
      return {
        ...state,
        buildError: null,
        errors: [],
        graphqlErrors: [],
        getServerDataError: null,
      }
    }
    default: {
      return state
    }
  }
}
const initialState = {
  errors: [],
  buildError: null,
  devSsrError: null,
  getServerDataError: null,
  graphqlErrors: [],
}
function DevOverlay({ children }) {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  React.useEffect(() => {
    const gatsbyEvents = window._gatsbyEvents || []
    window._gatsbyEvents = {
      push: ([channel, event]) => {
        if (channel === `FAST_REFRESH`) {
          dispatch(event)
        }
      },
    }
    gatsbyEvents.forEach(([channel, event]) => {
      if (channel === `FAST_REFRESH`) {
        dispatch(event)
      }
    })
    return () => {
      window._gatsbyEvents = []
    }
  }, [dispatch])
  const dismiss = () => {
    dispatch({ action: `DISMISS` })
    // Setting gatsbyEvents = [] is necessary for the runtime errors to correctly clear
    // However, using this for serverData errors doesn't work and results in the overlay not showing up
    // again since the component isn't remounted and thus the .push method is not reinitalized
    window._gatsbyEvents = []
  }
  const hasBuildError = state.buildError !== null
  const hasRuntimeErrors = Boolean(state.errors.length)
  const hasGetServerDataError = Boolean(state.getServerDataError)
  const hasGraphqlErrors = Boolean(state.graphqlErrors.length)
  const hasDevSsrError = state.devSsrError !== null
  const hasErrors =
    hasBuildError ||
    hasRuntimeErrors ||
    hasGraphqlErrors ||
    hasDevSsrError ||
    hasGetServerDataError
  // This component has a deliberate order (priority)
  const ErrorComponent = () => {
    if (hasBuildError) {
      return <BuildError error={state.buildError} />
    }
    if (hasGetServerDataError) {
      return <GetServerDataError error={state.getServerDataError} />
    }
    if (hasRuntimeErrors) {
      return <RuntimeErrors errors={state.errors} dismiss={dismiss} />
    }
    if (hasGraphqlErrors) {
      return <GraphqlErrors errors={state.graphqlErrors} dismiss={dismiss} />
    }
    if (hasDevSsrError) {
      return <DevSsrError error={state.devSsrError} />
    }
    return null
  }
  return (
    <React.Fragment>
      <ErrorBoundary hasErrors={hasErrors}>{children ?? null}</ErrorBoundary>
      {hasErrors ? (
        <ShadowPortal identifier="gatsby-fast-refresh">
          <Style />
          <ErrorComponent />
        </ShadowPortal>
      ) : undefined}
    </React.Fragment>
  )
}
export default DevOverlay