# Loading and Error State — Redux Toolkit / Zustand

Source: https://www.skillbyai.com/en/redux-zustand/a-errors

> Typed errors, unwrap and avoiding duplicate requests.

## Handling failures cleanly

Model request state as a status union (`'idle' | 'loading' | 'succeeded' | 'failed'`) rather than several booleans that can contradict each other. Use `rejectWithValue(value)` to return a typed, serialisable error payload (for example a server validation message) instead of a thrown `Error`. In a component, `await dispatch(thunk(arg)).unwrap()` returns the fulfilled payload or throws, which is handy for showing a toast after submit. The `condition` option can cancel a thunk before it starts, for example when a fetch is already in flight.

## rejectWithValue, condition and unwrap

A save thunk with typed errors.

```typescript
type ApiError = { message: string };

export const saveProfile = createAsyncThunk<
  User,                                  // fulfilled payload
  User,                                  // argument
  { state: RootState; rejectValue: ApiError }
>(
  'user/save',
  async (user, { rejectWithValue }) => {
    const res = await fetch('/api/profile', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(user),
    });
    if (!res.ok) return rejectWithValue({ message: `Save failed (${res.status})` });
    return (await res.json()) as User;
  },
  { condition: (_user, { getState }) => getState().user.status !== 'loading' }
);

// in a component
async function onSubmit(user: User) {
  try {
    await dispatch(saveProfile(user)).unwrap();
    toast('Saved');
  } catch (err) {
    toast((err as ApiError).message);
  }
}
```

## Errors in state must be serialisable

Store an error message or code string, not an Error object; the serialisability check will warn about class instances in state.

**Quiz:** What does .unwrap() on a dispatched thunk result do?

- [ ] Cancels the request
- [ ] Removes the thunk from the store
- [x] Returns the fulfilled payload or throws the rejection
- [ ] Converts the thunk into a slice reducer

*Answer:* Returns the fulfilled payload or throws the rejection. unwrap lets components use try/catch around a thunk.
