Separating UI states from API Request Status 

Obvious status quo that we never challenged

function MyComponent() {
  const {
    isLoading,
    error,
    hasFetched,
    isError,
    data,
  } = useApi(...)
  
  if (!isLoading && hasFetched && isError) {
    return <Error error={error} />
  }

  if (isLoading) {
    return <Spinner />
  }

  if (hasFetched && !isError) {
    return <RealUi />
  }
}

Lessons from Portal (or pretty much any other project anywhere else)

  • No way to ensure we make these checks everywhere.
     
  • Entropy and convolution
     
  • Facing the same problem for years and not once taking a step back and solving architecturally once and for all. (Skeletons, Error UIs, spinners, whatever else)
function MyComponent() {
  const {
    isLoading,
    error,
    hasFetched,
    isError,
    data,
  } = useApi(...)
  
  if (!isLoading && hasFetched && isError) {
    return <Error error={error} />
  }

  if (isLoading) {
    return <Spinner />
  }

  if (hasFetched && !isError) {
    return (
      <div>
        {data.items && data.items.length && <div>{data.items.map(item => item.name)}</div>}
        { /* after some runtime error in prod we add the next line */}
        {!data.items || !data.items.length && <div>No results found</div>}
      </div>
    )
}
the way forward

"But my app has different Error and Skeletons for each little UI block"

type ReactLeft<ComponentProps = {
  title?: string
  reason?: string
}> = { type: UI_STATES, props?: ComponentProps };


const error: ReactLeft<{ title: string }> = {
  type: UI_STATES.error,
  props: { 
    title: 'No privileges',
    reason: 'You are trying to access "creditCards_db" without read scopes.'
  }
}

const loading: ReactLeft = {
  type: UI_STATES.loading,
}

const invalid: ReactLeft<WildAssProps> = {
  type: UI_STATES.invalid,
  props: {
    some: 'random',
    prop: 'types',
    thatYour: 'component',
    needs: '!!!',
  }
}
import { RandomFactsAboutPotatoes, CuteBrokenPotatoGraphic } from './LeftComponents'

function Aloo() {
    const data: either.Either<ReactLeft, Potato[]> = useApi(...)

    return (
      <div className={layoutWrapper}>
        {
          pipe(
            data,
            either.fold(
              reactLeftToJSX({
                [UI_STATES.loading]: RandomFactsAboutPotatoes,
                [UI_STATES.error]: CuteBrokenPotatoGraphic,
              }),
              (user) => (
                <PotatoList />
              ),
            ),
          )
        }
      </div>
    )
}

La fin

ReactLeft

By Aviral Kulshreshtha