Building your application

Data Loading

Edit this page

SolidStart aims to make it easy to load data from your data sources to keep your UI updated with your data. For most of your data requirements, routes will likely be used to decide what data to load. SolidStart includes nested routing to help structure your application's UI in a hierarchical way, so that you can share layouts.


Data loading on the client

Solid provides a way to load data from your data sources using the createResource primitive. It takes an async function and returns a signal from it. createResource integrates with Suspense and ErrorBoundary to help manage lifecycle and error states.

import { For, createResource } from "solid-js";
type Users = { name: string; house: string };
export default function Page() {
const [users] = createResource(async () => {
const response = await fetch("https://example.com/users");
return (await response.json()) as User[];
});
return <ul>{users() && users()!.map((user) => <li>{user.name}</li>)}</ul>;
}

When fetching inside components, you can encounter unnecessary waterfalls, especially when nested under lazy loaded sections. To solve this, it can be valuable to introduce a hoist and cache mechanism.

Using a library like Tanstack Query can help with this.

for the example below we will be using the data in APIs in solid-router

Using some of the features of solid-router, we can create a cache for our data:

/routes/users.tsx
import { For } from "solid-js";
import { createAsync, cache } from "@solidjs/router";
type User = { name: string; email: string };
const getUsers = cache(async () => {
const response = await fetch("https://example.com/users");
return (await response.json()) as User[];
}, "users");
export const route = {
load: () => getUsers(),
};
export default function Page() {
const users = createAsync(() => getUsers());
return <ul>{users() && users()!.map((user) => <li>{user.name}</li>)}</ul>;
}

With this method, however, there are some caveats to be aware of:

  1. The load function is called once per route, which is the first time the user comes to that route. Following that, the fine-grained resources that remain alive synchronize with state/url changes to refetch data when needed. If the data needs a refresh, the refetch function returned in the createResource can be used.
  2. Before the route is rendered, the load function is called. It does not share the same context as the route. The context tree that is exposed to the load function is anything above the Routes component.
  3. On both the server and the client, the load function is called. The resources can avoid refetching if they 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

An advantage of being a full-stack JavaScript framework is that it is easy to write data loading code that can run both on the server and client. SolidStart offers a way to do that and more. Through the "use server" comment you can tell the bundler to create an RPC and not include the code in the clients bundle. This lets you write code that only runs on the server without needing to create an API route for it. For example, it could be database access or internal APIs, or when you sit within your function and need to use your server.

/routes/users.tsx
import { For } from "solid-js";
import { createAsync, cache } from "@solidjs/router";
type User = { name: string; email: string };
const getUsers = cache(async () => {
"use server";
return store.users.list();
}, "users");
export const route = {
load: () => getUsers(),
};
export default function Page() {
const users = createAsync(() => getUsers());
return <ul>{users() && users()!.map((user) => <li>{user.name}</li>)}</ul>;
}
Report an issue with this page