Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/extract-solid-ref.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ const ADVANCED_ROUTE_ORDER = {

const CANONICAL_ROUTES = {
createEffect: ["reactivity/create-effect.mdx", "Reactivity"],
createDeferred: ["reactivity/create-deferred.mdx", "Reactivity"],
createMemo: ["reactivity/create-memo.mdx", "Reactivity"],
createOptimistic: ["reactivity/create-optimistic.mdx", "Reactivity"],
createSignal: ["reactivity/create-signal.mdx", "Reactivity"],
Expand Down Expand Up @@ -613,6 +614,7 @@ const FOLD_INTO = {
ServerFunctionsClientConfig: "configureServerFunctionsClient",
PrepareRequestContext: "configureServerFunctionsClient",
PrepareRequestHook: "configureServerFunctionsClient",
ServerFunctionsSend: "configureServerFunctionsClient",
ServerFunctionsServerConfig: "configureServerFunctionsServer",
ServerFunctionEvent: "configureServerFunctionsServer",
HandleServerFunctionOptions: "handleServerFunctionRequest",
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(2)concepts/(5)mutations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -363,5 +363,5 @@ Remove the call and assert after the action's promise resolves.
- [Forms](/guides/forms): the same actions behind a `<form>`, with progressive enhancement, validation, and errors returned from the server.
- [Mutations and responses](/building-apps/server-functions/mutations-and-responses): writing `api.add` as a `"use server"` function and returning redirects, statuses, and safe errors.
- [Boundaries](/concepts/boundaries): where `Errored` catches the errors an action does not, and how an errored region recovers.
- [Data loading and mutations](/routing/solid-router/data): Solid Router's `action` and `query` add submissions, caching, and revalidation on top of these primitives.
- [Data loading and mutations](/routing/solid-router/data): with Solid Router mounted, the same actions call server functions and the router applies their revalidation and redirects; `query` adds caching and `formAction` adds forms.
- [State management](/guides/state-management): where the cart, the user, and the filter live, and why `createCart()` runs inside a provider.
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ Render `<form method="post" action={addToCartAction}>` and the form posts to the
The router marks the form `aria-busy` while it runs and, once it settles, records a submission whose `error` is the thrown envelope's value, which the [Forms guide](/guides/forms) reads to show messages next to the fields.

Keep the server function a named export and wrap it separately, as above, so a test or an API route can call `addToCart` without the form.
When the function exists only for the form, write the body inline, `formAction(async (form: FormData) => { "use server"; ... })`; the [Data loading and mutations](/routing/solid-router/data#mutate-with-actions) page uses that shape throughout, and the two behave the same.
When the function exists only for the form, write the body inline, `formAction(async (form: FormData) => { "use server"; ... })`; the two behave the same, and [Data loading and mutations](/routing/solid-router/data#add-a-form-with-formaction) shows both.

:::note[Which action]
`formAction` is from `@solidjs/router`; earlier releases called it `action`, and that name remains as a deprecated alias.
`formAction` is from `@solidjs/router`.
`solid-js` exports its own [`action`](/reference/solid-js/lifecycle-actions/action), which runs a generator as a reactive transaction and has no URL; [Mutations](/concepts/mutations) covers it.
A form needs the router's `formAction`.
:::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ The server-function runtime recognizes a browser form post at that URL, decodes

Application code reaches this through a router.
Solid Router's `formAction()` wraps the server function in a value that serializes to that URL, so the same `<form action={...}>` works before hydration and is taken over by the router after it.
Most applications need only the [router action](#post-a-form-through-a-router-action) section; the rest of the page is the transport underneath, for forms outside the router and for integrations that build their own form helpers.
Most applications need only the [form action](#post-a-form-through-a-form-action) section; the rest of the page is the transport underneath, for forms outside the router and for integrations that build their own form helpers.

## Post a form through a router action
## Post a form through a form action

Declare a server function that takes one `FormData` argument, and wrap it with `formAction` from `@solidjs/router`:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ It is the wrong one for a summary of a forty-message order dispute: the shopper

The difference is who owns the generation.
In the streaming guide the request owns it, and the reactive graph cancels it.
On this page a job on the server owns it: an action starts the job and returns, the job writes the reply into storage as the model produces it, and every page that shows the reply reads what is stored through a live source.
On this page a job on the server owns it: a server function starts the job and returns, the job writes the reply into storage as the model produces it, and every page that shows the reply reads what is stored through a live source.
Pick the request-scoped shape when the reader is the only consumer and an abandoned answer has no value; pick this shape when the reply must finish without its reader, be found again on reload, or be read from more than one tab.

Two deliveries are shown.
The client-owned one sends the reply as data through [`live()`](/reference/solid-web/server-functions/live) and renders it in the browser.
The server-owned one renders the whole thread on the server as a live server component, which is the shape of the `chat` example in the Solid repository; server components are an experimental preview, and that section says so.
The job, the storage, and the actions are the same in both.
The job, the storage, and the server functions are the same in both.

## The job

Three modules carry the shape: a store the job writes into, a registry of running jobs that no request owns, and the actions that start and stop one.
Three modules carry the shape: a store the job writes into, a registry of running jobs that no request owns, and the server functions that start and stop one.

The store keeps each reply and tells watchers when it changes:

Expand Down Expand Up @@ -115,7 +115,7 @@ The signal it receives is the job's own, not a request's: Stop aborts it, and no
A reply that fails keeps the text that had arrived, with the reason beside it; a reply that was stopped keeps its text and says so.
Write at most every few tens of milliseconds when the model is fast, as the example does, since every write wakes every watcher.

The actions start and stop a job and return at once:
The server functions start and stop a job and return at once:

```ts
// src/data/replies.ts
Expand Down Expand Up @@ -143,7 +143,7 @@ export const stopReply = formAction(async (replyId: string) => {
`sendMessage` stores the shopper's message and an empty reply with status `generating`, starts the job, and returns.
The response closes while the job is still writing; the server-function runtime does not dispose work a function started, so the job runs to its end whether or not anyone is watching.
`threads.append` is the thread half of the store, which keeps the message list and the history the model reads; the `chat` example's `db.send` is one.
`stopReply` is a Solid Router [form action](/routing/solid-router/data#mutate-with-actions), so the Stop button works as a form before JavaScript has loaded.
`stopReply` is a Solid Router [form action](/routing/solid-router/data#add-a-form-with-formaction), so the Stop button works as a form before JavaScript has loaded.
`sendMessage` is a plain server function the composer calls from code; the mounted router applies its response, revalidation included, the same way it does for a form.

:::caution[An orphan is a fact, not a hang]
Expand Down Expand Up @@ -398,7 +398,7 @@ Give the job an `AbortController` the registry owns, and pass `request.signal` o

### Stop does nothing

The Stop action aborted a controller the job was not reading, or the job was started by a server that has since restarted.
The Stop button aborted a controller the job was not reading, or the job was started by a server that has since restarted.
Check that `generations.start` passes `abort.signal` to the model call, and that the store marks orphaned replies `failed` on load.

### Two tabs show different replies
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(4)routing/(1)solid-router/(0)index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,5 @@ Read these in order if you are new to Solid Router, or jump to the one you need:
2. [Route definitions](/routing/solid-router/route-definitions): path patterns, parameter filters, route metadata, lazy subtrees, and the file-system manifest.
3. [Nested routes and layouts](/routing/solid-router/nested-routes): an account section with its own frame that stays mounted while its pages change.
4. [Navigation and typed paths](/routing/solid-router/navigation): `paths`, `useNavigate`, search parameters with a schema, active and pending links, and leave guards.
5. [Data loading and mutations](/routing/solid-router/data): `preload`, `query`, `formAction`, optimistic updates, and what revalidates after a mutation.
5. [Data loading and mutations](/routing/solid-router/data): `preload`, `query`, mutations through server functions and actions, forms with `formAction`, optimistic updates, and what revalidates.
6. [Server rendering and hydration](/routing/solid-router/server-rendering): what the server adds when the app renders there, and how a mutation returns fresh data in one round trip.
2 changes: 1 addition & 1 deletion src/routes/(4)routing/(1)solid-router/(1)setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ The other options exist for a specific situation each:
| `scrollRestoration` | The app manages scroll itself; set `false` to stop the router restoring the position on back and forward (on by default). |
| `transformUrl` | Incoming pathnames need rewriting before matching, for example to strip a locale prefix. |
| `singleFlight` | The router's server-function data consumer should be off; it is `true` by default. |
| `actionBase` | Server actions are served from a prefix other than the default `/_server`. |
| `actionBase` | Server functions are served from a prefix other than the default `/_server`. |

The router uses browser history by default on the client and the current request URL on the server, so `history` is rarely set in a web app:

Expand Down
4 changes: 2 additions & 2 deletions src/routes/(4)routing/(1)solid-router/(4)navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ A number moves through history: `navigate(-1)` is back.

:::tip[Let the server decide where to go]
A mutation that ends in a navigation does not need `useNavigate` at all.
Return `redirect(paths.account.orders(id))` from the server function or action and the router navigates when the response arrives, in the same update as the revalidation; the [Data](/routing/solid-router/data#what-revalidates-after-a-mutation) page covers that path.
Return `redirect(paths.account.orders(id))` from the server function and the router navigates when the response arrives, in the same update as the revalidation; the [Data](/routing/solid-router/data#what-revalidates-after-a-mutation) page covers that path.
:::

## Read the location
Expand Down Expand Up @@ -306,6 +306,6 @@ Add the schema with a transform to `Number`, and read with `useSearchParams(Rout
## Next steps

- [Nested routes and layouts](/routing/solid-router/nested-routes): where section navigation lives and what stays mounted across links.
- [Data loading and mutations](/routing/solid-router/data): how links preload ahead of a click, and `redirect()` from an action.
- [Data loading and mutations](/routing/solid-router/data): how links preload ahead of a click, and `redirect()` from a mutation.
- [State management](/guides/state-management#state-in-the-url): which page state belongs in the URL at all.
- [Navigation API reference](/reference/solid-router/navigation): signatures for every primitive on this page.
Loading
Loading