# Endpoint Error Policy

[Endpoint.errorPolicy](https://dataclient.io/rest/api/Endpoint.md#errorpolicy) controls cache behavior upon a fetch rejection.
It uses the rejection error to determine whether it should be treated as 'soft' or 'hard' error.

### Soft

Soft errors will continue showing valid data if it exists. However, if no previous data is in the store,
it will reject with `error`. In this case [useSuspense()](https://dataclient.io/docs/api/useSuspense.md) throws the
error to be caught by the nearest [ErrorBoundary](https://dataclient.io/docs/api/ErrorBoundary.md) or [AsyncBoundary](https://dataclient.io/docs/api/AsyncBoundary.md)

### Hard

Hard errors always reject with `error` - even when data has previously made available.

'hard' | `undefined` can both be used to indicate this state.

```ts title="api/lastUpdated"
import { Entity, RestEndpoint } from '@data-client/rest';
import { Temporal } from 'temporal-polyfill';

export class TimedEntity extends Entity {
  id = '';
  updatedAt = Temporal.Instant.fromEpochMilliseconds(0);

  static schema = {
    updatedAt: Temporal.Instant.from,
  };
}

export const lastUpdated = new RestEndpoint({
  path: '/api/currentTime/:id',
  schema: TimedEntity,
});
```

```ts title="getUpdated"
import { lastUpdated } from './api/lastUpdated';

export const getUpdated = lastUpdated.extend({
  fetch(this: any, arg) {
    // fail once with FAKE_ERROR when it is set
    const error = this.FAKE_ERROR;
    this.FAKE_ERROR = undefined;
    return error ? Promise.reject(error) : lastUpdated(arg);
  },
  errorPolicy: error =>
    error.status >= 500 ? ('soft' as const) : ('hard' as const),
  FAKE_ERROR: undefined as Error | undefined,
});

export const createError = (status: number) =>
  Object.assign(new Error('fake error'), { status });
```

```tsx title="TimePage"
import { useSuspense } from '@data-client/react';
import { getUpdated } from './getUpdated';

export default function TimePage({ id }) {
  const { updatedAt } = useSuspense(getUpdated, { id });
  return (
    <div>
      API time:{' '}
      <time>
        {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })}
      </time>
    </div>
  );
}
```

```tsx title="ShowTime"
import { AsyncBoundary, useController } from '@data-client/react';
import { getUpdated, createError } from './getUpdated';
import TimePage from './TimePage';

function ShowTime() {
  const ctrl = useController();
  return (
    <div>
      <AsyncBoundary fallback={<div>loading...</div>}>
        <TimePage id="1" />
      </AsyncBoundary>
      <div>
        <button
          onClick={() => {
            getUpdated.FAKE_ERROR = createError(500);
            ctrl.fetch(getUpdated, { id: '1' });
          }}
        >
          Fetch Soft
        </button>
        <button
          onClick={() => {
            getUpdated.FAKE_ERROR = createError(400);
            ctrl.fetch(getUpdated, { id: '1' });
          }}
        >
          Fetch Hard
        </button>
        <button
          onClick={() => {
            getUpdated.FAKE_ERROR = createError(500);
            ctrl.invalidate(getUpdated, { id: '1' });
          }}
        >
          Invalidate Soft
        </button>
        <button
          onClick={() => {
            getUpdated.FAKE_ERROR = createError(400);
            ctrl.invalidate(getUpdated, { id: '1' });
          }}
        >
          Invalidate Hard
        </button>
      </div>
    </div>
  );
}

render(
  <ResetableErrorBoundary>
    <ShowTime />
  </ResetableErrorBoundary>,
);
```

### Policy for RestEndpoint

Since `500`s indicate a failure of the server, we want to use stale data
if it exists. On the other hand, something like a `4xx` indicates 'user error', which
means the error indicates something about application flow - like if a record is deleted, resulting
in `404`. Keeping the record around would be inaccurate.

Since this is the typical behavior for REST APIs, this is the default policy in [@data-client/rest](https://www.npmjs.com/package/@data-client/rest)

```ts
errorPolicy(error) {
  return error.status >= 500 ? 'soft' : undefined;
}
```

`undefined` is another way of specifying a [hard error](#hard)
