solidjs / solidjs/solid-start

[Feature?]: Error Handling with solid-router

Open
#1,434 10 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
TypeScript
Stars
5.9k
Forks
426
Avg merge
1d 3h
Merged PRs (30d)
11

Description

Duplicates
  • I have searched the existing issues
Latest version
  • I have tested the latest version
Summary 💡

Allow for semantic error handling with server actions.
This is a continuation of https://github.com/solidjs/solid-router/discussions/365 discussion, but focused on solid-start server actions.

The goal is to allow:

  • return non-200 (401, 500, etc) responses from server actions for observability platforms to monitor how many responses are not 200.
  • handle such responses in code with reactive useSubmission().error when using without the form.

Proposal: use Response.ok to populate useSubmission().error.

const serverAction = action(() => {
  'use server';
  try {
    return perform();
  } catch(error) {
    // Error is serialised, no queries are revalidated (unless specified otherwise)
    // Response.ok is `false`, so `submission.error` is serialised Error.
    // This will make the action use to fail (as the error is rethrown), so needs try/catch
    return json(new Error('Something went wrong'), { code: 500 });
  }
});

const Component = () => {
  const performAction = useAction(serverAction);
  const submission = useSubmission(serverAction);

  // See example, QR Code scanning
  const userActionWithoutForm = () => {
    try {
      await performAction(); // same as fetch().catch() when the request has failed
    } catch {}
  }

  return (
    <Switch>
      <Match when={submission.pending}>Checking</Match>
      <Match when={submission.error}>Retry with another input</Match>
      <Match when={true}>Perform action</Match>
    </Switch>
  )
}

If the action returns non-Error data, the action does not fail:

// action, does not throw an Error
  return json('Something went wrong', { code: 500 });
// Component
  // similar to fetch(response => response.json()) without checking for response.ok
  await performAction();
  // rely on submission.error being populated with 'Something went wrong' string
Examples 🌈

In the app, users can scan a QR code, when valid code is scanned – action is executed. If scanned code is invalid – error message is shown with Retry logic:

const checkCode = action(async code => {
  "use server";
  try {
    return validity(code);
  } catch(error) {
    // ===Unclear===
  }
});
// component:
const submission = useSubmission(checkCode);
const checkCode = useAction(checkCode);

const onScanned = code => {
  try {
    await checkCode(code);
  } catch(error) {
    // do something, or just ignore it, same way we would do with using fetch directly.
  }
}

return (
  <Switch>
    <Match when={submission.pending}>Checking</Match>
    <Match when={submission.error}>Wrong code</Match>
    <Match when={true}><QRScanner onSuccess={onScanned} /></Match>
  </Switch>
);
Motivation 🔦

Right now, to return non-200 response from a server action, one needs to use custom Response:

'use server';
return Response({ error: 'Something went wrong' }, { code: 500 });

This, however, makes useSubmission(action).error to be empty, because the response is a valid Response:

https://github.com/solidjs/solid-router/blob/e773947b85ac78281816e86621a2cdb6735ae95a/src/data/action.ts#L149

Making the data type from the action ambiguous: { correctResponse } | { error }, which makes handling of the response complicated:

const performAction = useAction(serverAction);
const submission = useSubmission(serverAction);
const [error, setError] = createSignal(null);
const someUserAction = () => {
  try {
    const data = performAction();
    if (data.error) {
    // do nothing, rely on submission.result.error
    // but submission.error !== submission.result.error
    // by design to distinguish failures?
    } else {} // success logic
  } catch(error) {
    // will never get here
  }
}

So, another option is to make sure submission.error is populated, throw an error from the action.

const serverAction = action(() => {
  'use server';
  throw new Error('Something went wrong');
});

This works for useSubmission().error, but the HTTP Response status is now 200, so one needs custom metrics to monitor how the API is performing.

Doing return json(new Error('error'), { status: 500, revalidate: [] }) is making action to fail itself (?). So 500, but all queries are revalidated (?) and submission.error empty.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/data/action.ts at the linked line and trace how action responses populate useSubmission().error and how non-200 responses are handled. Compare the behavior against the server-action examples in the issue, then define tests for Error and non-Error response data, HTTP status preservation, revalidation, and reactive submission state.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.