diff --git a/src/routes/reference/basic-reactivity/create-resource.mdx b/src/routes/reference/basic-reactivity/create-resource.mdx index 2ebcfb4de..fd975456e 100644 --- a/src/routes/reference/basic-reactivity/create-resource.mdx +++ b/src/routes/reference/basic-reactivity/create-resource.mdx @@ -11,212 +11,270 @@ tags: - loading - error-handling - ssr -version: '1.0' +version: "1.0" description: >- Fetch async data with createResource. Handles loading states, errors, and integrates with Suspense for seamless data fetching in Solid.js applications. --- -`createResource` takes an asynchronous fetcher function and returns a signal that is updated with the resulting data when the fetcher completes. +Creates a reactive resource that manages asynchronous data fetching and loading states, automatically tracking dependencies and providing a simple interface for reading, refreshing, and error handling. +It integrates with Solid's reactivity system and Suspense boundaries. -There are two ways to use `createResource`: you can pass the fetcher function as the sole argument, or you can additionally pass a source signal as the first argument. -The source signal will retrigger the fetcher whenever it changes, and its value will be passed to the fetcher. +## Import -```tsx -const [data, { mutate, refetch }] = createResource(fetchData) +```typescript +import { createResource } from "solid-js"; ``` -```tsx -const [data, { mutate, refetch }] = createResource(source, fetchData) +## Type + +```typescript +// Without source +function createResource( + fetcher: ResourceFetcher, + options?: ResourceOptions +): ResourceReturn; + +// With source +function createResource( + source: ResourceSource, + fetcher: ResourceFetcher, + options?: ResourceOptions +): ResourceReturn; ``` -In these snippets, the fetcher is the function `fetchData`, and `data()` is undefined until `fetchData` finishes resolving. -In the first case, `fetchData` will be called immediately. -In the second, `fetchData` will be called as soon as `source` has any value other than false, null, or undefined. -It will be called again whenever the value of `source` changes, and that value will always be passed to `fetchData` as its first argument. - -You can call `mutate` to directly update the `data` signal (it works like any other signal setter). -You can also call refetch to rerun the fetcher directly, and pass an optional argument to provide additional info to the fetcher e.g `refetch(info)`. - -`data` works like a normal signal getter: use `data()` to read the last returned value of `fetchData`. -But it also has extra reactive properties: - -- `data.loading`: whether the fetcher has been called but not returned. -- `data.error`: if the request has errored out. - `createResource`: provides an `Error` object for `data.error`. It will show even if the fetcher throws something else. - - - Fetcher throws an `Error` instance, `data.error` will be that instance. - - If the fetcher throws a string, `data.error.message` will contain that string. - - When the fetcher throws a value that is neither an `Error` nor a string, that value will be available as `data.error.cause`. - -- As of **v1.4.0**, `data.latest` returns the last value received and will not trigger [Suspense](/reference/components/suspense) or [transitions](#TODO); if no value has been returned yet, `data.latest` will act the same as `data()`. - This can be useful if you want to show the out-of-date data while the new data is loading. - -`loading`, `error`, and `latest` are reactive getters and can be tracked. - -## The fetcher - -The `fetcher` is the async function that you provide to `createResource` to actually fetch the data. -It is passed two arguments: the value of the source signal (if provided), and an info object with two properties: `value` and `refetching`. -The `value` property tells you the previously fetched value. -The `refetching` property is true if the `fetcher` was triggered using the refetch function and false otherwise. -If the `refetch` function was called with an argument (`refetch(info)`), refetching is set to that argument. - -```tsx -async function fetchData(source, { value, refetching }) { - // Fetch the data and return a value. - //`source` tells you the current value of the source signal; - //`value` tells you the last returned value of the fetcher; - //`refetching` is true when the fetcher is triggered by calling `refetch()`, - // or equal to the optional data passed: `refetch(info)` +### Related types + +```typescript +type ResourceReturn = [Resource, ResourceActions]; + +type Resource = { + (): T | undefined; + state: "unresolved" | "pending" | "ready" | "refreshing" | "errored"; + loading: boolean; + error: any; + latest: T | undefined; +}; + +type ResourceActions = { + mutate: (value: T | undefined) => T | undefined; + refetch: (info?: R) => Promise | T | undefined; +}; + +type ResourceSource = + | S + | false + | null + | undefined + | (() => S | false | null | undefined); + +type ResourceFetcher = ( + source: S, + info: { value: T | undefined; refetching: R | boolean } +) => T | Promise; + +interface ResourceOptions { + initialValue?: T; + name?: string; + deferStream?: boolean; + ssrLoadFrom?: "initial" | "server"; + storage?: ( + init: T | undefined + ) => [Accessor, Setter]; + onHydrated?: (k: S | undefined, info: { value: T | undefined }) => void; } +``` + +## Parameters + +### `source` + +- **Type:** `ResourceSource` +- **Default:** `undefined` + +Reactive data source whose non-nullish and non-false values are passed to the fetcher. +When the source changes, the fetcher is automatically re-run. + +### `fetcher` + +- **Type:** `ResourceFetcher` + +Function that receives the source value (or `true` if no source), the current resource info, and returns a value or Promise. + +### `options` + +- **Type:** `ResourceOptions` +- **Default:** `{}` + +Configuration options for the resource. + +#### `initialValue` + +- **Type:** `T` +- **Default:** `undefined` + +Initial value for the resource. +When provided, the resource starts in "ready" state and the type excludes `undefined`. + +#### `name` + +- **Type:** `string` +- **Default:** `undefined` + +A name for debugging purposes in development mode. + +#### `deferStream` + +- **Type:** `boolean` +- **Default:** `false` + +Controls streaming behavior during server-side rendering. + +#### `ssrLoadFrom` -const [data, { mutate, refetch }] = createResource(getQuery, fetchData) +- **Type:** `"initial" | "server"` +- **Default:** `"server"` -// read value -data() +Determines how the resource loads during SSR hydration. -// check if loading -data.loading +- "server": Uses the server-fetched value during hydration. +- "initial": Re-fetches on the client after hydration. -// check if errored -data.error +#### `storage` -// directly set value without creating promise -mutate(optimisticValue) +- **Type:** `(init: T | undefined) => [Accessor, Setter]` +- **Default:** `createSignal` -// refetch the last request explicitly -refetch() +Custom storage function for the resource value, useful for persistence or custom state management. +#### `onHydrated` + +- **Type:** `(k: S | undefined, info: { value: T | undefined }) => void` +- **Default:** `undefined` + +Callback fired when the resource hydrates on the client side. + +## Return Value + +- **Type:** `[Resource, ResourceActions]` + +Returns a tuple containing the resource accessor and resource actions. + +### `Resource` + +```typescript +type Resource = { + (): T | undefined; + state: "unresolved" | "pending" | "ready" | "refreshing" | "errored"; + loading: boolean; + error: any; + latest: T | undefined; +}; +``` + +- `state`: Current state of the resource. + See table below for state descriptions. +- `loading`: Indicates if the resource is currently loading. +- `error`: Error information if the resource failed to load. +- `latest`: The latest value of the resource. + +| State | Description | Loading | Error | Latest | +| ------------ | --------------------------------------- | ------- | ----------- | ----------- | +| `unresolved` | Initial state, not yet fetched | `false` | `undefined` | `undefined` | +| `pending` | Fetching in progress | `true` | `undefined` | `undefined` | +| `ready` | Successfully fetched | `false` | `undefined` | `T` | +| `refreshing` | Refetching while keeping previous value | `true` | `undefined` | `T` | +| `errored` | Fetching failed | `false` | `any` | `undefined` | + +### `ResourceActions` + +```typescript +type ResourceActions = { + mutate: (value: T | undefined) => T | undefined; + refetch: (info?: R) => Promise | T | undefined; +}; ``` -## Version 1.4.0 and Later +- `mutate`: Function to Manually overwrite the resource value without calling the fetcher. + Allows you to optimistically update the resource value locally, without making a network request. +- `refetch`: Function to re-run the fetcher without changing the source. + If a parameter is provided to `refetch`, it will be passed to the fetcher's `refetching` property. + +## Examples + +### Basic usage -#### v1.4.0 +```typescript +const [data] = createResource(async () => { + const response = await fetch("/api/data"); + return response.json(); +}); -If you're using `renderToStream`, you can tell Solid to wait for a resource before flushing the stream using the `deferStream` option: +// Access data +console.log(data()); // undefined initially, then fetched data +console.log(data.loading); // true during fetch +console.log(data.state); // "pending" → "ready" +``` + +### With Source + +```typescript +const [userId, setUserId] = createSignal(1); -```tsx -// fetches a user and streams content as soon as possible -const [user] = createResource(() => params.id, fetchUser) +const [user] = createResource(userId, async (id) => { + const response = await fetch(`/api/users/${id}`); + return response.json(); +}); -// fetches a user but only streams content after this resource has loaded -const [user] = createResource(() => params.id, fetchUser, { - deferStream: true, -}) +// Automatically refetches when userId changes +setUserId(2); ``` -#### v1.5.0 +### With Actions -1. We've added a new state field which covers a more detailed view of the Resource state beyond `loading` and `error`. -You can now check whether a Resource is `unresolved`, `pending`, `ready`, `refreshing`, or `errored`. +```typescript +const [posts, { refetch, mutate }] = createResource(fetchPosts); + +// Manual refetch +await refetch(); + +// Optimistic update +mutate((posts) => [...posts, newPost]); +``` -| State | Value resolved | Loading | Has error | -| ------------ | -------------- | ------- | --------- | -| `unresolved` | No | No | No | -| `pending` | No | Yes | No | -| `ready` | Yes | No | No | -| `refreshing` | Yes | Yes | No | -| `errored` | No | No | Yes | +### Error handling -2. When server-rendering resources, especially when embedding Solid in other systems that fetch data before rendering, you might want to initialize the resource with this prefetched value instead of fetching again and having the resource serialize it in its own state. -You can use the new `ssrLoadFrom` option for this. -Instead of using the default `server` value, you can pass `initial` and the resource will use `initialValue` as if it were the result of the first fetch for both SSR and hydration. +```typescript +const [data] = createResource(async () => { + const response = await fetch('/api/data'); + if (!response.ok) throw new Error('Failed to fetch'); + return response.json(); +}); -```tsx -const [data, { mutate, refetch }] = createResource(() => params.id, fetchUser, { - initialValue: preloadedData, - ssrLoadFrom: "initial", -}) +// In JSX +Error loading data}> +
{data()?.title}
+
``` -3. Resources can be set with custom defined storage with the same signature as a Signal by using the storage option. -For example using a custom reconciling store could be done this way: - -```tsx -function createDeepSignal(value: T): Signal { - const [store, setStore] = createStore({ - value, - }) - return [ - () => store.value, - (v: T) => { - const unwrapped = unwrap(store.value) - typeof v === "function" && (v = v(unwrapped)) - setStore("value", reconcile(v)) - return store.value - }, - ] as Signal -} +### With Initial Value + +```typescript +const [user] = createResource(() => fetchUser(), { + initialValue: { name: "Loading...", id: 0 }, +}); -const [resource] = createResource(fetcher, { - storage: createDeepSignal, -}) +// user() is never undefined +console.log(user().name); // "Loading..." initially ``` -This option is still experimental and may change in the future. - -## Options - -The `createResource` function takes an optional third argument, an options object. The options are: - -| Name | Type | Default | Description | -| ------------ | ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | `string` | `undefined` | A name for the resource. This is used for debugging purposes. | -| deferStream | `boolean` | `false` | If true, Solid will wait for the resource to resolve before flushing the stream. | -| initialValue | `any` | `undefined` | The initial value of the resource. | -| onHydrated | `function` | `undefined` | A callback that is called when the resource is hydrated. | -| ssrLoadFrom | `"server" \| "initial"` | `"server"` | The source of the initial value for SSR. If set to `"initial"`, the resource will use the `initialValue` option instead of the value returned by the fetcher. | -| storage | `function` | `createSignal` | A function that returns a signal. This can be used to create a custom storage for the resource. This is still experimental | - -## Note for TypeScript users - -The function and type definitions for `createResource` are as follows: - -```tsx -import { createResource } from "solid-js" -import type { ResourceReturn, ResourceOptions } from "solid-js" - -type ResourceReturn = [ - { - (): T | undefined - state: "unresolved" | "pending" | "ready" | "refreshing" | "errored" - loading: boolean - error: any - latest: T | undefined - }, - { - mutate: (v: T | undefined) => T | undefined - refetch: (info: unknown) => Promise | T - } -] - -type ResourceOptions = { - initialValue?: T - name?: string - deferStream?: boolean - ssrLoadFrom?: "initial" | "server" - storage?: ( - init: T | undefined - ) => [Accessor, Setter] - onHydrated?: (k: S | undefined, info: { value: T | undefined }) => void -} +### Conditional Fetching -function createResource( - fetcher: ( - k: U, - info: { value: T | undefined; refetching: boolean | unknown } - ) => T | Promise, - options?: ResourceOptions -): ResourceReturn - -function createResource( - source: U | false | null | (() => U | false | null), - fetcher: ( - k: U, - info: { value: T | undefined; refetching: boolean | unknown } - ) => T | Promise, - options?: ResourceOptions -): ResourceReturn +```typescript +const [isEnabled, setIsEnabled] = createSignal(false); +const [data] = createResource( + () => isEnabled() && userId(), // Only fetches when enabled and userId exists + async (id) => fetchUserData(id) +); ``` diff --git a/src/routes/reference/basic-reactivity/create-signal.mdx b/src/routes/reference/basic-reactivity/create-signal.mdx index f6de896ac..a21608575 100644 --- a/src/routes/reference/basic-reactivity/create-signal.mdx +++ b/src/routes/reference/basic-reactivity/create-signal.mdx @@ -9,153 +9,117 @@ tags: - reactivity - core - primitives -version: '1.0' +version: "1.0" description: >- Create reactive state with createSignal, Solid's fundamental primitive. Track values that change over time and automatically update your UI when they do. --- -Signals are the most basic reactive primitive. -They track a single value (which can be a value of any type) that changes over time. +Creates a reactive state primitive consisting of a getter (accessor) and a setter function that forms the foundation of Solid's reactivity system. +Signals are optimized for frequent reads and infrequent writes -```tsx -import { createSignal } from "solid-js" - -function createSignal( - initialValue: T, - options?: { - equals?: false | ((prev: T, next: T) => boolean) - name?: string - internal?: boolean - } -): [get: () => T, set: (v: T) => T] - -// available types for return value of createSignal: -import type { Signal, Accessor, Setter } from "solid-js" -type Signal = [get: Accessor, set: Setter] -type Accessor = () => T -type Setter = (v: T | ((prev?: T) => T)) => T +## Import +```typescript +import { createSignal } from "solid-js"; ``` -The Signal's value starts out equal to the passed first argument `initialValue` (or undefined if there are no arguments). -The `createSignal` function returns a pair of functions as a two-element array: a getter (or accessor) and a setter. -In typical use, you would destructure this array into a named Signal like so: +## Type Signature -```tsx -const [count, setCount] = createSignal(0) -const [ready, setReady] = createSignal(false) +```typescript +function createSignal(): Signal; +function createSignal(value: T, options?: SignalOptions): Signal; ``` -Calling the getter (e.g., `count()` or `ready()`) returns the current value of the Signal. +### Related Types -Crucial to automatic dependency tracking, calling the getter within a tracking scope causes the calling function to depend on this Signal, so that function will rerun if the Signal gets updated. +```typescript +type Signal = [get: Accessor, set: Setter]; -Calling the setter (e.g., `setCount(nextCount)` or `setReady(nextReady)`) sets the Signal's value and updates the Signal (triggering dependents to rerun) if the value actually changed (see details below). -The setter takes either the new value for the signal or a function that maps the previous value of the signal to a new value as its only argument. -The updated value is also returned by the setter. As an example: +type Accessor = () => T; -```tsx -// read signal's current value, and -// depend on signal if in a tracking scope -// (but nonreactive outside of a tracking scope): -const currentCount = count() +type Setter = { + (value: Exclude | ((prev: T) => U)): U; + (value: (prev: T) => U): U; + (value: Exclude): U; + (value: Exclude | ((prev: T) => U)): U; +}; -// or wrap any computation with a function, -// and this function can be used in a tracking scope: -const doubledCount = () => 2 * count() +interface SignalOptions { + name?: string; + equals?: false | ((prev: T, next: T) => boolean); + internal?: boolean; +} +``` -// or build a tracking scope and depend on signal: -const countDisplay =
{count()}
+## Parameters -// write signal by providing a value: -setReady(true) +### `value` -// write signal by providing a function setter: -const newCount = setCount((prev) => prev + 1) +- **Type:** `T` +- **Default:** `undefined` -``` +The initial value for the signal. If no initial value is provided, the signal's type is automatically extended with `undefined`. -:::note - If you want to store a function in a Signal you must use the function form: +### `options` - ```tsx - setValue(() => myFunction); - ``` +- **Type:** `SignalOptions` +- **Default:** `undefined` - However, functions are not treated specially as the `initialValue` argument to `createSignal`, so you can pass a - function initial value as is: +Configuration object for the signal. +In production mode, all debugging metadata is stripped away for optimal performance. +Dev-only options like `name` are ignored, and no warnings are issued, ensuring that your application runs as efficiently as possible. - ```tsx - const [func, setFunc] = createSignal(myFunction); - ``` +#### `name` -::: +- **Type:** `string` +- **Default:** `undefined` -## Options +A name for the signal used for debugging purposes in development mode. +The name will show up in console messages and in the [Solid devtools](https://github.com/thetarnav/solid-devtools). -| Name | Type | Default | Description | -| ---------- | ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `equals` | `false \| ((prev: T, next: T) => boolean)` | `===` | A function that determines whether the Signal's value has changed. If the function returns true, the Signal's value will not be updated and dependents will not rerun. If the function returns false, the Signal's value will be updated and dependents will rerun. | -| `name` | `string` | | A name for the Signal. This is useful for debugging. | -| `internal` | `boolean` | `false` | If true, the Signal will not be accessible in the devtools. | +#### `equals` -### `equals` +- **Type:** `false | ((prev: T, next: T) => boolean)` +- **Default:** `false` -The `equals` option can be used to customize the equality check used to determine whether the Signal's value has changed. -By default, the equality check is a strict equality check (`===`). -If you want to use a different equality check, you can pass a custom function as the `equals` option. -The custom function will be called with the previous and next values of the Signal as arguments. -If the function returns true, the Signal's value will not be updated and dependents will not rerun. -If the function returns false, the Signal's value will be updated and dependents will rerun. +By default, signals use reference equality (`===`). +A custom comparison function to determine when the signal should update. +If set to `false`, the signal will always update regardless of value equality. +This can be useful to create a Signal that triggers manual updates in the reactive system, but must remain a pure function. -```tsx -const [count, setCount] = createSignal(0, { - equals: (prev, next) => prev === next, -}) -``` +#### `internal` -Here are some examples of this option in use: +- **Type:** `boolean` +- **Default:** `false` -```tsx -// use { equals: false } to allow modifying object in-place; -// normally this wouldn't be seen as an update because the -// object has the same identity before and after change -const [object, setObject] = createSignal({ count: 0 }, { equals: false }) -setObject((current) => { - current.count += 1 - current.updated = new Date() - return current -}) - -// use { equals: false } to create a signal that acts as a trigger without storing a value: -const [depend, rerun] = createSignal(undefined, { equals: false }) -// now calling depend() in a tracking scope -// makes that scope rerun whenever rerun() gets called - -// define equality based on string length: -const [myString, setMyString] = createSignal("string", { - equals: (newVal, oldVal) => newVal.length === oldVal.length, -}) - -setMyString("string") // considered equal to the last value and won't cause updates -setMyString("stranger") // considered different and will cause updates -``` +Marks the signal as internal, preventing it from appearing in development tools. +This is primarily used by Solid's internal systems. -### `name` +## Return Value -The `name` option can be used to give the Signal a name. -This is useful for debugging. The name will be displayed in the devtools. +- **Type:** `Signal` -```tsx -const [count, setCount] = createSignal(0, { name: "count" }) -``` +Returns a tuple `[getter, setter]` where: + +- **getter**: An accessor function that returns the current value and tracks dependencies when called within a reactive context +- **setter**: A function that updates the signal's value and notifies all dependent computations -### `internal` +## Examples -The `internal` option can be used to hide the Signal from the devtools. -This is useful for Signals that are used internally by a component and should not be exposed to the user. +### Basic usage ```tsx -const [count, setCount] = createSignal(0, { internal: true }) +import { createSignal } from "solid-js"; + +function Counter() { + const [count, setCount] = createSignal(0); + + return ( +
+ + {count()} +
+ ); +} ```