diff --git a/src/middleware/legacy-routes-redirect.ts b/src/middleware/legacy-routes-redirect.ts index fdf371f9b..647d1b38b 100644 --- a/src/middleware/legacy-routes-redirect.ts +++ b/src/middleware/legacy-routes-redirect.ts @@ -180,6 +180,8 @@ const LEGACY_ROUTES = { "/solid-router/reference/response-helpers/revalidate": "/solid-router/reference/data-apis/revalidate", + + "/solid-start/guides/data-loading": "/solid-start/guides/data-fetching", } as const; function isLegacyRoute(path: string): path is keyof typeof LEGACY_ROUTES { diff --git a/src/routes/concepts/effects.mdx b/src/routes/concepts/effects.mdx index adb73f395..fbe55d14e 100644 --- a/src/routes/concepts/effects.mdx +++ b/src/routes/concepts/effects.mdx @@ -37,12 +37,6 @@ createEffect(() => { In this example, an effect is created that logs the current value of `count` to the console. When the value of `count` changes, the effect is triggered, causing it to run again and log the new value of `count`. -:::note -Effects are primarily intended for handling side effects that do not write to the reactive system. -It's best to avoid setting signals within effects, as this can lead to additional rendering or even infinite loops if not managed carefully. -Instead, it is recommended to use [createMemo](/reference/basic-reactivity/create-memo) to compute new values that rely on other reactive values. -::: - ## Managing dependencies Effects can be set to observe any number of dependencies. diff --git a/src/routes/guides/routing-and-navigation.mdx b/src/routes/guides/routing-and-navigation.mdx index 9e3d8ad0d..f69bbb764 100644 --- a/src/routes/guides/routing-and-navigation.mdx +++ b/src/routes/guides/routing-and-navigation.mdx @@ -507,7 +507,7 @@ render( ``` `[id].jsx` contains the component that gets rendered. -When you wrap the function within [`createAsync`](/solid-router/reference/data-apis/create-async) with the imported function, it will yield [a signal](/concepts/signals) once the anticipated promise resolves. +When you wrap the function within [`createAsync`](/solid-router/reference/data-apis/create-async) with the imported function, it will yield [a signal](/routes/concepts/signals) once the anticipated promise resolves. ```jsx // [id].jsx diff --git a/src/routes/reference/basic-reactivity/create-effect.mdx b/src/routes/reference/basic-reactivity/create-effect.mdx index 84ee13f12..760632341 100644 --- a/src/routes/reference/basic-reactivity/create-effect.mdx +++ b/src/routes/reference/basic-reactivity/create-effect.mdx @@ -10,165 +10,109 @@ tags: - reactivity - lifecycle - cleanup -version: "1.0" +version: '1.0' description: >- Learn how to use createEffect to run side effects when reactive dependencies change. Perfect for DOM manipulation and external library integration. --- -The `createEffect` primitive creates a reactive computation. -It automatically tracks reactive values, such as [signals](/concepts/signals), accessed within the provided function. -This function will re-run whenever any of its dependencies change. - -## Execution Timing - -### Initial Run - -- The initial run of effects is **scheduled to occur after the current rendering phase completes**. -- It runs after all synchronous code in a component has finished and DOM elements have been created, but **before the browser paints them on the screen**. -- **[Refs](/concepts/refs) are set** before the first run, even though DOM nodes may not yet be attached to the main document tree. - This is relevant when using the [`children`](/reference/component-apis/children) helper. - -### Subsequent Runs - -- After the initial run, the effect **re-runs whenever any tracked dependency changes**. -- When multiple dependencies change within the same batch, the effect **runs once per batch**. -- The **order of runs** among multiple effects is **not guaranteed**. -- Effects always run **after** all pure computations (such as [memos](/concepts/derived-values/memos)) within the same update cycle. - -### Server-Side Rendering - -- Effects **never run during SSR**. -- Effects also **do not run during the initial client hydration**. - -## Import +```tsx +import { createEffect } from "solid-js" -```ts -import { createEffect } from "solid-js"; -``` +function createEffect(fn: (v: T) => T, value?: T): void -## Type - -```ts -function createEffect( - fn: EffectFunction, Next> -): void; -function createEffect( - fn: EffectFunction, - value: Init, - options?: { name?: string } -): void; -function createEffect( - fn: EffectFunction, - value?: Init, - options?: { name?: string } -): void; ``` -## Parameters - -### `fn` - -- **Type:** `EffectFunction | EffectFunction` -- **Required:** Yes +Effects are a general way to make arbitrary code ("side effects") run whenever dependencies change, e.g., to modify the DOM manually. +`createEffect` creates a new computation that runs the given function in a tracking scope, thus automatically tracking its dependencies, and automatically reruns the function whenever the dependencies update. -A function to be executed as the effect. +For example: -It receives the value returned from the previous run, or the initial `value` during the first run. -The value returned by `fn` is passed to the next run. - -### `value` - -- **Type:** `Init` -- **Required:** No - -The initial value passed to `fn` during its first run. +```tsx +const [a, setA] = createSignal(initialValue) -### `options` +// effect that depends on signal `a` +createEffect(() => doSideEffect(a())) +``` -- **Type:** `{ name?: string }` -- **Required:** No +The effect will run whenever `a` changes value. -An optional configuration object with the following properties: +The effect will also run once, immediately after it is created, to initialize the DOM to the correct state. This is called the "mounting" phase. +However, we recommend using `onMount` instead, which is a more explicit way to express this. -#### `name` +The effect callback can return a value, which will be passed as the `prev` argument to the next invocation of the effect. +This is useful for memoizing values that are expensive to compute. For example: -- **Type:** `string` -- **Required:** No +```tsx +const [a, setA] = createSignal(initialValue) + +// effect that depends on signal `a` +createEffect((prevSum) => { + // do something with `a` and `prevSum` + const sum = a() + prevSum + if (sum !== prevSum) console.log("sum changed to", sum) + return sum +}, 0) +// ^ the initial value of the effect is 0 +``` -A name for the effect, which can be useful for identification in debugging tools like the [Solid Debugger](https://github.com/thetarnav/solid-devtools). +Effects are meant primarily for side effects that read but don't write to the reactive system: it's best to avoid setting signals in effects, which without care can cause additional rendering or even infinite effect loops. Instead, prefer using [createMemo](/reference/basic-reactivity/create-memo) to compute new values that depend on other reactive values, so the reactive system knows what depends on what, and can optimize accordingly. +If you do end up setting a signal within an effect, computations subscribed to that signal will be executed only once the effect completes; see [`batch`](/reference/reactive-utilities/batch) for more detail. -## Return value +The first execution of the effect function is not immediate; it's scheduled to run after the current rendering phase (e.g., after calling the function passed to [render](/reference/rendering/render), [createRoot](/reference/reactive-utilities/create-root), or [runWithOwner](/reference/reactive-utilities/run-with-owner)). +If you want to wait for the first execution to occur, use [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) (which runs before the browser renders the DOM) or `await Promise.resolve()` or `setTimeout(..., 0)` (which runs after browser rendering). -`createEffect` does not return a value. +```tsx +// assume this code is in a component function, so is part of a rendering phase +const [count, setCount] = createSignal(0) + +// this effect prints count at the beginning and when it changes +createEffect(() => console.log("count =", count())) +// effect won't run yet +console.log("hello") +setCount(1) // effect still won't run yet +setCount(2) // effect still won't run yet + +queueMicrotask(() => { + // now `count = 2` will print + console.log("microtask") + setCount(3) // immediately prints `count = 3` + console.log("goodbye") +}) + +// --- overall output: --- +// hello +// count = 2 +// microtask +// count = 3 +// goodbye +``` -## Examples +This delay in first execution is useful because it means an effect defined in a component scope runs after the JSX returned by the component gets added to the DOM. +In particular, [refs](/reference/jsx-attributes/ref) will already be set. +Thus you can use an effect to manipulate the DOM manually, call vanilla JS libraries, or other side effects. -### Basic Usage +Note that the first run of the effect still runs before the browser renders the DOM to the screen (similar to React's `useLayoutEffect`). +If you need to wait until after rendering (e.g., to measure the rendering), you can use `await Promise.resolve()` (or `Promise.resolve().then(...)`), but note that subsequent use of reactive state (such as signals) will not trigger the effect to rerun, as tracking is not possible after an async function uses `await`. +Thus you should use all dependencies before the promise. -```tsx -import { createSignal, createEffect } from "solid-js"; - -function Counter() { - const [count, setCount] = createSignal(0); - - // Every time count changes, this effect re-runs. - createEffect(() => { - console.log("Count incremented! New value: ", count()); - }); - - return ( -
-

Count: {count()}

- -
- ); -} -``` +If you'd rather an effect run immediately even for its first run, use [createRenderEffect](/reference/secondary-primitives/create-render-effect) or [createComputed](/reference/secondary-primitives/create-computed). -### Execution Timing +You can clean up your side effects in between executions of the effect function by calling [onCleanup](/reference/lifecycle/on-cleanup) inside the effect function. +Such a cleanup function gets called both in between effect executions and when the effect gets disposed (e.g., the containing component unmounts). +For example: ```tsx -import { createSignal, createEffect, createRenderEffect } from "solid-js"; - -function Counter() { - const [count, setCount] = createSignal(0); - - // This is part of the component's synchronous execution. - console.log("Hello from counter"); - - // This effect is scheduled to run after the initial render is complete. - createEffect(() => { - console.log("Effect:", count()); - }); - - // By contrast, a render effect runs synchronously during the render phase. - createRenderEffect(() => { - console.log("Render effect:", count()); - }); - - // Setting a signal during the render phase re-runs render effects, but not effects, which are - // still scheduled. - setCount(1); - - // A microtask is scheduled to run after the current synchronous code (the render phase) finishes. - queueMicrotask(() => { - // Now that rendering is complete, signal updates will trigger effects immediately. - setCount(2); - }); -} - -// Output: -// Hello from counter -// Render effect: 0 -// Render effect: 1 -// Effect: 1 -// Render effect: 2 -// Effect: 2 +// listen to event dynamically given by eventName signal +createEffect(() => { + const event = eventName() + const callback = (e) => console.log(e) + ref.addEventListener(event, callback) + onCleanup(() => ref.removeEventListener(event, callback)) +}) ``` -## Related +## Arguments -- [`createRenderEffect`](/reference/secondary-primitives/create-render-effect) -- [`onCleanup`](/reference/lifecycle/on-cleanup) -- [`onMount`](/reference/lifecycle/on-mount) +- `fn` - The function to run in a tracking scope. It can return a value, which will be passed as the `prev` argument to the next invocation of the effect. +- `value` - The initial value of the effect. This is useful for memoizing values that are expensive to compute. diff --git a/src/routes/reference/reactive-utilities/create-root.mdx b/src/routes/reference/reactive-utilities/create-root.mdx index cd633a8ab..207b403fa 100644 --- a/src/routes/reference/reactive-utilities/create-root.mdx +++ b/src/routes/reference/reactive-utilities/create-root.mdx @@ -9,121 +9,21 @@ tags: - scopes - disposal - tracking -version: "1.0" +version: '1.0' description: >- Create non-tracked owner scopes in SolidJS for manual memory management. Essential for nested tracking scopes and preventing auto-disposal. --- -The `createRoot` function creates a new owned context, which requires explicit disposal of computations it owns. - -## Import - -```ts -import { createRoot } from "solid-js"; -``` - -## Type - -```ts -function createRoot( - fn: (dispose: () => void) => T, - detachedOwner?: Owner -): T; -``` - -## Parameters - -### `fn` - -- **Type:** `(dispose: () => void) => T` -- **Required:** Yes - -The function executes within a newly created owned context. -The computations created within this function are managed by the root and will only be disposed of when the provided `dispose` function is called. - -If a function is passed without a `dispose` parameter, an unowned root is created. -In this case, the computations are not managed for disposal, which may lead to memory leaks. - -This function itself does not track dependencies and only runs once. - -### `detachedOwner` - -- **Type:** `Owner` -- **Required:** No - -An optional owner that establishes the root's position in the ownership hierarchy. -When provided, the root becomes owned by this owner and inherits its contextual state (such as [contexts](/concepts/context)). - -## Return Value - -`createRoot` returns the value returned by the `fn` function. - -## Examples - -### Basic Usage - -```ts -import { createSignal, createEffect, createRoot } from "solid-js"; - -function createCounter(initial = 0) { - const [count, setCount] = createSignal(initial); - - createEffect(() => { - console.log(`Count changed, new value: ${count()}`); - }); - - function increment() { - setCount((c) => c + 1); - } - - function reset() { - setCount(initial); - } - - return { count, increment, reset }; -} - -test("createCounter works correctly", () => { - createRoot((dispose) => { - const { count, increment, reset } = createCounter(10); - - expect(count()).toBe(10); - increment(); - expect(count()).toBe(11); - reset(); - expect(count()).toBe(10); - - dispose(); - }); -}); -``` - -### Returning Values - ```ts -import { createRoot, createSignal, onCleanup } from "solid-js"; - -const counter = createRoot((dispose) => { - const [count, setCount] = createSignal(0); - - onCleanup(() => { - console.log("Dispose was called!"); - }); +import { createRoot } from "solid-js" - return { - value: count, - increment: () => setCount((c) => c + 1), - dispose, - }; -}); +function createRoot(fn: (dispose: () => void) => T): T -console.log(counter.value()); // 0 -counter.increment(); -console.log(counter.value()); // 1 -counter.dispose(); // Logs "Dispose was called!" ``` -## Related +Creates a new non-tracked owner scope that doesn't auto-dispose. +This is useful for nested tracking scopes that you do not wish to release when the parent re-evaluates. -- [`render`](/reference/rendering/render) +All Solid code should be wrapped in one of these top level as they ensure that all memory/computations are freed up. +Normally you do not need to worry about this as createRoot is embedded into all render entry functions. diff --git a/src/routes/reference/secondary-primitives/create-render-effect.mdx b/src/routes/reference/secondary-primitives/create-render-effect.mdx index 03ba5016b..0c5083f14 100644 --- a/src/routes/reference/secondary-primitives/create-render-effect.mdx +++ b/src/routes/reference/secondary-primitives/create-render-effect.mdx @@ -10,164 +10,62 @@ tags: - immediate - refs - lifecycle -version: "1.0" +version: '1.0' description: >- Execute effects immediately during rendering with createRenderEffect. Run side effects as DOM creates, before refs are set or connected. --- -The `createRenderEffect` primitive creates a reactive computation that automatically tracks reactive values, such as [signals](/concepts/signals), accessed within the provided function. -This function re-runs whenever any of its dependencies change. - -## Execution Timing - -### Initial Run - -- A render effect runs **synchronously during the current rendering phase**, while DOM elements are being created or updated. -- It **runs before elements are mounted** to the DOM. -- **[Refs](/concepts/refs) are not set** during this initial run. - -### Subsequent Runs - -- After the initial render, the render effect **re-runs whenever any of its tracked dependencies change**. -- Re-runs occur **after** all pure computations (such as [memos](/concepts/derived-values/memos)) have completed within the same update cycle. -- When multiple dependencies change within the same batch, the render effect **runs once per batch**. -- The **order of re-runs** among multiple render effects is **not guaranteed**. - -### Server-Side Rendering - -- During SSR, render effects **run once on the server**, since they are part of the synchronous rendering phase. -- On the client, an initial run still occurs during the client rendering phase to initialize the reactive system; - that client initial run is separate from the server run. -- After hydration, subsequent runs occur on the client when dependencies change. - -## Import - ```ts -import { createRenderEffect } from "solid-js"; -``` +import { createRenderEffect } from "solid-js" -## Type +function createRenderEffect(fn: (v: T) => T, value?: T): void -```ts -function createRenderEffect( - fn: EffectFunction, Next> -): void; -function createRenderEffect( - fn: EffectFunction, - value: Init, - options?: { name?: string } -): void; -function createRenderEffect( - fn: EffectFunction, - value?: Init, - options?: { name?: string } -): void; ``` -## Parameters - -### `fn` - -- **Type:** `EffectFunction | EffectFunction` -- **Required:** Yes - -A function to be executed as the render effect. - -It receives the value returned from the previous run, or the initial `value` during the first run. -The value returned by `fn` is passed to the next run. - -### `value` - -- **Type:** `Init` -- **Required:** No - -The initial value passed to `fn` during its first run. - -### `options` - -- **Type:** `{ name?: string }` -- **Required:** No +A render effect is a computation similar to a regular effect (as created by [`createEffect`](/reference/basic-reactivity/create-effect)), but differs in when Solid schedules the first execution of the effect function. +While `createEffect` waits for the current rendering phase to be complete, `createRenderEffect` immediately calls the function. +Thus the effect runs as DOM elements are being created and updated, but possibly before specific elements of interest have been created, and probably before those elements have been connected to the document. +In particular, **refs** will not be set before the initial effect call. +Indeed, Solid uses `createRenderEffect` to implement the rendering phase itself, including setting of **refs**. -An optional configuration object with the following properties: +Reactive updates to render effects are identical to effects: they queue up in response to a reactive change (e.g., a single signal update, or a batch of changes, or collective changes during an entire render phase) and run in a single [`batch`](/reference/reactive-utilities/batch) afterward (together with effects). +In particular, all signal updates within a render effect are batched. -#### `name` +Here is an example of the behavior. (Compare with the example in [`createEffect`](/reference/basic-reactivity/create-effect).) -- **Type:** `string` -- **Required:** No - -A name for the render effect, which can be useful for identification in debugging tools like the [Solid Debugger](https://github.com/thetarnav/solid-devtools). - -## Return value - -`createRenderEffect` does not return a value. - -## Examples - -### Basic Usage - -```tsx -import { createSignal, createRenderEffect } from "solid-js"; - -function Counter() { - const [count, setCount] = createSignal(0); - - // This runs immediately during render, and re-runs when the count changes. - createRenderEffect(() => { - console.log("Count: ", count()); - }); - - return ( -
-

Count: {count()}

- -
- ); -} +```ts +// assume this code is in a component function, so is part of a rendering phase +const [count, setCount] = createSignal(0) + +// this effect prints count at the beginning and when it changes +createRenderEffect(() => console.log("count =", count())) +// render effect runs immediately, printing `count = 0` +console.log("hello") +setCount(1) // effect won't run yet +setCount(2) // effect won't run yet + +queueMicrotask(() => { + // now `count = 2` will print + console.log("microtask") + setCount(3) // immediately prints `count = 3` + console.log("goodbye") +}) + +// --- overall output: --- +// count = 0 [this is the only added line compared to createEffect] +// hello +// count = 2 +// microtask +// count = 3 +// goodbye ``` -### Execution Timing - -```tsx -import { createSignal, createEffect, createRenderEffect } from "solid-js"; - -function Counter() { - const [count, setCount] = createSignal(0); - - // This is part of the component's synchronous execution. - console.log("Hello from counter"); - - // This effect is scheduled to run after the initial render is complete. - createEffect(() => { - console.log("Effect:", count()); - }); - - // By contrast, a render effect runs synchronously during the render phase. - createRenderEffect(() => { - console.log("Render effect:", count()); - }); - - // Setting a signal during the render phase re-runs render effects, but not effects, which are - // still scheduled. - setCount(1); - - // A microtask is scheduled to run after the current synchronous code (the render phase) finishes. - queueMicrotask(() => { - // Now that rendering is complete, signal updates will trigger effects immediately. - setCount(2); - }); -} - -// Output: -// Hello from counter -// Render effect: 0 -// Render effect: 1 -// Effect: 1 -// Render effect: 2 -// Effect: 2 -``` +Just like `createEffect`, the effect function gets called with an argument equal to the value returned from the effect function's last execution, or on the first call, equal to the optional second argument of `createRenderEffect`. -## Related +## Arguments -- [`createEffect`](/reference/basic-reactivity/create-effect) -- [`onCleanup`](/reference/lifecycle/on-cleanup) +| Name | Type | Description | +| :------ | :------------ | :----------------------------------------------------- | +| `fn` | `(v: T) => T` | The effect function to be called. | +| `value` | `T` | The initial value to be passed to the effect function. | diff --git a/src/routes/solid-router/concepts/actions.mdx b/src/routes/solid-router/concepts/actions.mdx index 25c09e7bc..a04eaea67 100644 --- a/src/routes/solid-router/concepts/actions.mdx +++ b/src/routes/solid-router/concepts/actions.mdx @@ -17,151 +17,445 @@ description: >- isomorphic data flows with progressive enhancement support. --- -When developing applications, it is common to need to communicate new information to the server based on user interactions. -Actions are Solid Router’s solution to this problem. +Many user interactions in an application involve changing data on the server. +These **mutations** can be challenging to manage, as they require updates to the application's state and proper error handling. +Actions simplify managing data mutations. -## What are actions? +Actions provide several benefits: -Actions are asynchronous processing functions that allow you to submit data to your server and receive a response. -They are isomorphic, meaning they can run either on the server or the client, depending on what is needed. -This flexibility makes actions a powerful tool for managing and tracking data submissions. +- **Integrated state management:** + Solid Router automatically tracks the execution state of an action, simplifying reactive UI feedback. +- **Automatic data revalidation:** + After an action successfully completes, Solid Router revalidates relevant [`queries`](/solid-router/concepts/queries), ensuring the UI reflects the latest data. +- **Progressive enhancement:** + When used with HTML forms, actions enable functionality even if JavaScript is not yet loaded. -### How actions work +## Defining actions -Actions represent the server-side part of an [HTML form](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form). -They handle submissions through POST requests, allowing you to easily use HTML forms to send data. +Actions are defined by wrapping the data-mutation logic with the [`action` function](/solid-router/reference/data-apis/action). -When a user performs an action, such as submitting a form, the data is sent to the server for processing via an action. +```tsx +import { action } from "@solidjs/router"; + +const createTicketAction = action(async (subject: string) => { + const response = await fetch("https://my-api.com/support/tickets", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ subject }), + }); + + if (!response.ok) { + const errorData = await response.json(); + return { ok: false, message: errorData.message }; + } + + return { ok: true }; +}, "createTicket"); +``` + +In this example, an action is defined that creates a support ticket using a remote API. + +## Using actions + +Actions can be triggered in two ways: using a HTML [`
` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form) or programmatically using the [`useAction` primitive](/solid-router/reference/data-apis/use-action). -### Benefits of using actions +The recommended approach is to use a `` element. +This ensures a robust user experience with progressive enhancement, since the form works even without JavaScript. -1. **Isomorphic**: Since actions can run on both the server and client, you can optimize performance by choosing the best execution environment for your needs. -2. **Asynchronous processing**: Actions handle data submissions asynchronously, ensuring that your application remains responsive. -3. **Simplified data handling**: By using actions, the process of managing and tracking data submissions can be streamlined, reducing the complexity of your application. +For cases where a form is not suitable, the [`useAction` primitive](/solid-router/reference/data-apis/use-action) can be used to trigger the action programmatically. -## Creating actions +### With the `` element -To create an action, use the `action` function from the `@solidjs/router` package. -This function takes an asynchronous function as an argument and returns a new function that can be used to submit data. +Solid Router extends the standard HTML `` element to work with actions. +Form submissions can be handled using action by passing an action to the `action` prop. + +Consider these points when using actions with ``: + +1. The `` element **must** have `method="post"`. +2. The action function will automatically receive the form's data as a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object as its first parameter. +3. For SSR environments, a unique name **must** be provided as the second parameter to the `action` function. + This name is used by Solid Router to identify and serialize the action across the client and server. ```tsx import { action } from "@solidjs/router"; -const echo = action(async (message: string) => { - // Simulates an asynchronous operation, such as an API call - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); - console.log(message); -}); +const submitFeedbackAction = action(async (formData: FormData) => { + const message = formData.get("message")?.toString(); + // ... Sends the feedback to the server. +}, "submitFeedback"); + +function FeedbackForm() { + return ( + +