Data loading
What's a modern app without some data to power it? SolidStart aims to make it easy to load data from your data sources. It will help you keep your UI updated with your data. For most of your data requirements, you will likely be using the route to decide what data to load.
The URL is the primary way of navigating around your app. SolidStart has nested routing to help structure your app's UI in a hierarchical way, so that you can share layouts.
Solid has a createResource
primitive that takes an async function and returns a signal from it. It's a great starting place for your data needs. It integrates with Suspense
and ErrorBoundary
to help you manage your lifecycle. Let's take a look at how we can use this to load data from a third party API for our app.
tsx
import { ForCreates a list elements from a list
it receives a map function as its child that receives a list element and an accessor with the index and returns a JSX-Element; if the list is empty, an optional fallback is returned:
```typescript
<For each={items} fallback={<div>No items</div>}>
{(item, index) => <div data-index={index()}>{item}</div>}
</For>
```
If you have a list with fixed indices and changing values, consider using `<Index>` instead.
(alias) function For<T extends readonly any[], U extends JSX.Element>(props: {
each: T | undefined | null | false;
fallback?: JSX.Element;
children: (item: T[number], index: Accessor<number>) => U;
}): JSX.Element
import For
, createResourceCreates a resource that wraps a repeated promise in a reactive pattern:
```typescript
// Without source
const [resource, { mutate, refetch }] = createResource(fetcher, options);
// With source
const [resource, { mutate, refetch }] = createResource(source, fetcher, options);
```
(alias) function createResource<T, R = unknown>(fetcher: ResourceFetcher<true, T, R>, options: InitializedResourceOptions<NoInfer<T>, true>): InitializedResourceReturn<T, R> (+3 overloads)
import createResource
} from "solid-js";
type Studenttype Student = {
name: string;
house: string;
}
= { name: string; house: string };
export default function Pagefunction Page(): JSX.Element
() { const [studentsconst students: Resource<Student[]>
] = createResourceCreates a resource that wraps a repeated promise in a reactive pattern:
```typescript
// Without source
const [resource, { mutate, refetch }] = createResource(fetcher, options);
// With source
const [resource, { mutate, refetch }] = createResource(source, fetcher, options);
```
(alias) createResource<Student[], unknown>(fetcher: ResourceFetcher<true, Student[], unknown>, options?: ResourceOptions<Student[], true> | undefined): ResourceReturn<...> (+3 overloads)
import createResource
(async () => { const response = await fetch[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
function fetch(input: RequestInfo | URL, init?: RequestInit | undefined): Promise<Response>
("https://hogwarts.deno.dev/students"); return (await response.json[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
(method) Body.json(): Promise<any>
()) as Studenttype Student = {
name: string;
house: string;
}
[]; });
return <ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>{studentsconst students: () => Student[] | undefined
() && studentsconst students: () => Student[] | undefined
()!.mapCalls a defined callback function on each element of an array, and returns an array that contains the results.
(method) Array<Student>.map<JSX.Element>(callbackfn: (value: Student, index: number, array: Student[]) => JSX.Element, thisArg?: any): JSX.Element[]
(student(parameter) student: Student
=> <li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>{student(parameter) student: Student
.name}</li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>)}</ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>; }
tsx
import { ForCreates a list elements from a list
it receives a map function as its child that receives a list element and an accessor with the index and returns a JSX-Element; if the list is empty, an optional fallback is returned:
```typescript
<For each={items} fallback={<div>No items</div>}>
{(item, index) => <div data-index={index()}>{item}</div>}
</For>
```
If you have a list with fixed indices and changing values, consider using `<Index>` instead.
(alias) function For<T extends readonly any[], U extends JSX.Element>(props: {
each: T | undefined | null | false;
fallback?: JSX.Element;
children: (item: T[number], index: Accessor<number>) => U;
}): JSX.Element
import For
, createResourceCreates a resource that wraps a repeated promise in a reactive pattern:
```typescript
// Without source
const [resource, { mutate, refetch }] = createResource(fetcher, options);
// With source
const [resource, { mutate, refetch }] = createResource(source, fetcher, options);
```
(alias) function createResource<T, R = unknown>(fetcher: ResourceFetcher<true, T, R>, options: InitializedResourceOptions<NoInfer<T>, true>): InitializedResourceReturn<T, R> (+3 overloads)
import createResource
} from "solid-js";
type Studenttype Student = {
name: string;
house: string;
}
= { name: string; house: string };
export default function Pagefunction Page(): JSX.Element
() { const [studentsconst students: Resource<Student[]>
] = createResourceCreates a resource that wraps a repeated promise in a reactive pattern:
```typescript
// Without source
const [resource, { mutate, refetch }] = createResource(fetcher, options);
// With source
const [resource, { mutate, refetch }] = createResource(source, fetcher, options);
```
(alias) createResource<Student[], unknown>(fetcher: ResourceFetcher<true, Student[], unknown>, options?: ResourceOptions<Student[], true> | undefined): ResourceReturn<...> (+3 overloads)
import createResource
(async () => { const response = await fetch[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
function fetch(input: RequestInfo | URL, init?: RequestInit | undefined): Promise<Response>
("https://hogwarts.deno.dev/students"); return (await response.json[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
(method) Body.json(): Promise<any>
()) as Studenttype Student = {
name: string;
house: string;
}
[]; });
return <ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>{studentsconst students: () => Student[] | undefined
() && studentsconst students: () => Student[] | undefined
()!.mapCalls a defined callback function on each element of an array, and returns an array that contains the results.
(method) Array<Student>.map<JSX.Element>(callbackfn: (value: Student, index: number, array: Student[]) => JSX.Element, thisArg?: any): JSX.Element[]
(student(parameter) student: Student
=> <li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>{student(parameter) student: Student
.name}</li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>)}</ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>; }
However, fetching inside your components can cause unnecessary waterfalls especially when nested under lazy loaded sections. To solve that it can be valuable to introduce a hoist and cache mechanism.
Libraries like Tanstack Query enable this but for the example below we will be using the data in APIs in @solidjs/router
.
/routes/students.tsx
tsx
import { ForCreates a list elements from a list
it receives a map function as its child that receives a list element and an accessor with the index and returns a JSX-Element; if the list is empty, an optional fallback is returned:
```typescript
<For each={items} fallback={<div>No items</div>}>
{(item, index) => <div data-index={index()}>{item}</div>}
</For>
```
If you have a list with fixed indices and changing values, consider using `<Index>` instead.
(alias) function For<T extends readonly any[], U extends JSX.Element>(props: {
each: T | undefined | null | false;
fallback?: JSX.Element;
children: (item: T[number], index: Accessor<number>) => U;
}): JSX.Element
import For
} from "solid-js"; import { createAsync(alias) function createAsync<T>(fn: (prev: T) => Promise<T>, options: {
name?: string;
initialValue: T;
deferStream?: boolean;
}): Accessor<T> (+1 overload)
import createAsync
, cache(alias) function cache<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>
(alias) namespace cache
import cache
} from "@solidjs/router";
type Studenttype Student = {
name: string;
house: string;
}
= { name: string; house: string };
const getStudentsconst getStudents: ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
= cache(alias) cache<() => Promise<Student[]>>(fn: () => Promise<Student[]>, name: string): ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
import cache
(async () => { const response = await fetch[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
function fetch(input: RequestInfo | URL, init?: RequestInit | undefined): Promise<Response>
("https://hogwarts.deno.dev/students"); return (await response.json[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
(method) Body.json(): Promise<any>
()) as Studenttype Student = {
name: string;
house: string;
}
[]; }, "students");
export const routeconst route: {
load: () => Promise<Student[]>;
}
= { load(property) load: () => Promise<Student[]>
: () => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
() };
export default function Pagefunction Page(): JSX.Element
() { const studentsconst students: Accessor<Student[] | undefined>
= createAsync(alias) createAsync<Student[]>(fn: (prev: Student[] | undefined) => Promise<Student[]>, options?: {
name?: string | undefined;
initialValue?: Student[] | undefined;
deferStream?: boolean | undefined;
} | undefined): Accessor<...> (+1 overload)
import createAsync
(() => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
());
return <ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>{studentsconst students: () => Student[] | undefined
() && studentsconst students: () => Student[] | undefined
()!.mapCalls a defined callback function on each element of an array, and returns an array that contains the results.
(method) Array<Student>.map<JSX.Element>(callbackfn: (value: Student, index: number, array: Student[]) => JSX.Element, thisArg?: any): JSX.Element[]
(student(parameter) student: Student
=> <li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>{student(parameter) student: Student
.name}</li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>)}</ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>; }
/routes/students.tsx
tsx
import { ForCreates a list elements from a list
it receives a map function as its child that receives a list element and an accessor with the index and returns a JSX-Element; if the list is empty, an optional fallback is returned:
```typescript
<For each={items} fallback={<div>No items</div>}>
{(item, index) => <div data-index={index()}>{item}</div>}
</For>
```
If you have a list with fixed indices and changing values, consider using `<Index>` instead.
(alias) function For<T extends readonly any[], U extends JSX.Element>(props: {
each: T | undefined | null | false;
fallback?: JSX.Element;
children: (item: T[number], index: Accessor<number>) => U;
}): JSX.Element
import For
} from "solid-js"; import { createAsync(alias) function createAsync<T>(fn: (prev: T) => Promise<T>, options: {
name?: string;
initialValue: T;
deferStream?: boolean;
}): Accessor<T> (+1 overload)
import createAsync
, cache(alias) function cache<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>
(alias) namespace cache
import cache
} from "@solidjs/router";
type Studenttype Student = {
name: string;
house: string;
}
= { name: string; house: string };
const getStudentsconst getStudents: ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
= cache(alias) cache<() => Promise<Student[]>>(fn: () => Promise<Student[]>, name: string): ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
import cache
(async () => { const response = await fetch[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
function fetch(input: RequestInfo | URL, init?: RequestInit | undefined): Promise<Response>
("https://hogwarts.deno.dev/students"); return (await response.json[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
(method) Body.json(): Promise<any>
()) as Studenttype Student = {
name: string;
house: string;
}
[]; }, "students");
export const routeconst route: {
load: () => Promise<Student[]>;
}
= { load(property) load: () => Promise<Student[]>
: () => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
() };
export default function Pagefunction Page(): JSX.Element
() { const studentsconst students: Accessor<Student[] | undefined>
= createAsync(alias) createAsync<Student[]>(fn: (prev: Student[] | undefined) => Promise<Student[]>, options?: {
name?: string | undefined;
initialValue?: Student[] | undefined;
deferStream?: boolean | undefined;
} | undefined): Accessor<...> (+1 overload)
import createAsync
(() => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
());
return <ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>{studentsconst students: () => Student[] | undefined
() && studentsconst students: () => Student[] | undefined
()!.mapCalls a defined callback function on each element of an array, and returns an array that contains the results.
(method) Array<Student>.map<JSX.Element>(callbackfn: (value: Student, index: number, array: Student[]) => JSX.Element, thisArg?: any): JSX.Element[]
(student(parameter) student: Student
=> <li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>{student(parameter) student: Student
.name}</li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>)}</ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>; }
Caveats:
- The
load
function is only called once per route, the first time the user comes to that route. After that, the fine-grained resources that remain alive synchronize with state/url changes to refetch data when needed. If you need to refresh the data, you can use the refetch
function that is returned by createResource
.
- The
load
function is called before the route is rendered. It doesn't share the same context
as the route. The context tree that is exposed to the load
function is anything above the Page
component.
- The
load
function will be called both on the server and the client. It's the resources that can avoid refetching if they had serialized their data in the server render.
- The server-side render will only wait for the resources to fetch and serialize if the resource signals are accessed under a
Suspense
boundary.
Data loading always on the server
The primary advantage of being a full-stack Javascript framework is that its easy to write data loading code that can run both on the server and client. SolidStart gives them superpowers. You might want to write code that only runs on your server but didn't want to create an API route for it.
It could be database access, or internal APIs, etc. It could sit within your functions where you need to use your server. We use "use server";
for this. It's a special comment that tells the bundler to create an RPC and not include the code in the client bundle .
/routes/students.tsx
tsx
import { ForCreates a list elements from a list
it receives a map function as its child that receives a list element and an accessor with the index and returns a JSX-Element; if the list is empty, an optional fallback is returned:
```typescript
<For each={items} fallback={<div>No items</div>}>
{(item, index) => <div data-index={index()}>{item}</div>}
</For>
```
If you have a list with fixed indices and changing values, consider using `<Index>` instead.
(alias) function For<T extends readonly any[], U extends JSX.Element>(props: {
each: T | undefined | null | false;
fallback?: JSX.Element;
children: (item: T[number], index: Accessor<number>) => U;
}): JSX.Element
import For
} from "solid-js"; import { createAsync(alias) function createAsync<T>(fn: (prev: T) => Promise<T>, options: {
name?: string;
initialValue: T;
deferStream?: boolean;
}): Accessor<T> (+1 overload)
import createAsync
, cache(alias) function cache<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>
(alias) namespace cache
import cache
} from "@solidjs/router";
type Studenttype Student = {
name: string;
house: string;
}
= { name: string; house: string };
const getStudentsconst getStudents: ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
= cache(alias) cache<() => Promise<Student[]>>(fn: () => Promise<Student[]>, name: string): ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
import cache
(async () => { "use server";
return hogwartsconst hogwarts: {
students: {
list(): Student[];
};
}
.students(property) students: {
list(): Student[];
}
.list(method) list(): Student[]
(); }, "students");
export const routeconst route: {
load: () => Promise<Student[]>;
}
= { load(property) load: () => Promise<Student[]>
: () => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
() };
export default function Pagefunction Page(): JSX.Element
() { const studentsconst students: Accessor<Student[] | undefined>
= createAsync(alias) createAsync<Student[]>(fn: (prev: Student[] | undefined) => Promise<Student[]>, options?: {
name?: string | undefined;
initialValue?: Student[] | undefined;
deferStream?: boolean | undefined;
} | undefined): Accessor<...> (+1 overload)
import createAsync
(() => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
());
return <ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>{studentsconst students: () => Student[] | undefined
() && studentsconst students: () => Student[] | undefined
()!.mapCalls a defined callback function on each element of an array, and returns an array that contains the results.
(method) Array<Student>.map<JSX.Element>(callbackfn: (value: Student, index: number, array: Student[]) => JSX.Element, thisArg?: any): JSX.Element[]
(student(parameter) student: Student
=> <li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>{student(parameter) student: Student
.name}</li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>)}</ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>; }
/routes/students.tsx
tsx
import { ForCreates a list elements from a list
it receives a map function as its child that receives a list element and an accessor with the index and returns a JSX-Element; if the list is empty, an optional fallback is returned:
```typescript
<For each={items} fallback={<div>No items</div>}>
{(item, index) => <div data-index={index()}>{item}</div>}
</For>
```
If you have a list with fixed indices and changing values, consider using `<Index>` instead.
(alias) function For<T extends readonly any[], U extends JSX.Element>(props: {
each: T | undefined | null | false;
fallback?: JSX.Element;
children: (item: T[number], index: Accessor<number>) => U;
}): JSX.Element
import For
} from "solid-js"; import { createAsync(alias) function createAsync<T>(fn: (prev: T) => Promise<T>, options: {
name?: string;
initialValue: T;
deferStream?: boolean;
}): Accessor<T> (+1 overload)
import createAsync
, cache(alias) function cache<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>
(alias) namespace cache
import cache
} from "@solidjs/router";
type Studenttype Student = {
name: string;
house: string;
}
= { name: string; house: string };
const getStudentsconst getStudents: ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
= cache(alias) cache<() => Promise<Student[]>>(fn: () => Promise<Student[]>, name: string): ((...args: never[]) => Promise<Student[]>) & {
keyFor: () => string;
key: string;
}
import cache
(async () => { "use server";
return hogwartsconst hogwarts: {
students: {
list(): Student[];
};
}
.students(property) students: {
list(): Student[];
}
.list(method) list(): Student[]
(); }, "students");
export const routeconst route: {
load: () => Promise<Student[]>;
}
= { load(property) load: () => Promise<Student[]>
: () => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
() };
export default function Pagefunction Page(): JSX.Element
() { const studentsconst students: Accessor<Student[] | undefined>
= createAsync(alias) createAsync<Student[]>(fn: (prev: Student[] | undefined) => Promise<Student[]>, options?: {
name?: string | undefined;
initialValue?: Student[] | undefined;
deferStream?: boolean | undefined;
} | undefined): Accessor<...> (+1 overload)
import createAsync
(() => getStudentsconst getStudents: (...args: never[]) => Promise<Student[]>
());
return <ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>{studentsconst students: () => Student[] | undefined
() && studentsconst students: () => Student[] | undefined
()!.mapCalls a defined callback function on each element of an array, and returns an array that contains the results.
(method) Array<Student>.map<JSX.Element>(callbackfn: (value: Student, index: number, array: Student[]) => JSX.Element, thisArg?: any): JSX.Element[]
(student(parameter) student: Student
=> <li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>{student(parameter) student: Student
.name}</li(property) JSX.HTMLElementTags.li: JSX.LiHTMLAttributes<HTMLLIElement>
>)}</ul(property) JSX.HTMLElementTags.ul: JSX.HTMLAttributes<HTMLUListElement>
>; }