Mojang / ore-ui

💎 Building blocks to construct game UIs using web tech.
https://react-facet.mojang.com/
MIT License
409 stars 18 forks source link

Describing and handling sharedFacet errors #125

Closed Shenato closed 9 months ago

Shenato commented 1 year ago

This PR adds a way to define errors for SharedFacets in the SharedFacetDriver using the onError callback like so

const sharedFacetDriver: SharedFacetDriver = (name, onChange, onError) => {
  if (name === 'bar') {
    onError?.({ facetName: 'bar', facetError: 'facetUnavailable' })
    return () => {}
  }
  onChange({ bar: 'testing 123', values: ['a', 'b', 'c'] })
  return () => {}
}

The sharedFacetDriver defines an error case, which calls an onError handler which should be set by either a specific useSharedFacet() instance or one of the new <SharedFacetsErrorBoundary /> instances which should catch any errors in any facet within it's scope and handle the error with the error handler you pass to it

Example usage of a local facet error handler

useSharedFacet(barFacet, (error) => {
  doSomethingWithError(error)
  maybeUnmountMyFailingComponent(false)
}),

Example of the usage of a <SharedFacetsErrorBoundary />

const [mount, setMount] = useFacetState(true)
return (
  <SharedFacetsErrorBoundary
    onError={(error) => {
      doSomethingElseWithError(error)
      unmountMyFailingScreen(false)
    }}
  >
    <Mount when={mount}>{children}</Mount>
  </SharedFacetsErrorBoundary>
)

The error you get passed to your callback from the sharedFacetDriver is of type SharedFacetError and it's basic type looks like so

export interface SharedFacetError {
  facetName: string
  message?: string
}

And the the end user can re-define what the error type should look like using the declaration merging typescript feature, we went with this implementation to make sure the end user gets typesafety in their error handling which they can re-define to suit their needs.

// This is how we've opted to allow users to extend the error type to their usecases, they re-declare the
// interface for SharedErrorFacet with whatever extensions they want
// the original type just has facetName as mandatory and we add ontop of that facetError as mandatory in this case
declare module '@mojang/shared-facet' {
  export interface SharedFacetError {
    facetError: string
  }
}

The end result of the type in the user's codebase would look something like this.

export interface SharedFacetError {
  facetName: string
  message?: string
  facetError: string
}
Shenato commented 9 months ago

A new facet API might be introduced soon which will render this PR obselete, consider taking up the effort again after such change is done