A declarative JavaScript library for building user interfaces with fine-grained reactivity.
—
Resource system for handling asynchronous data loading with built-in loading states, error handling, and automatic refetching capabilities.
Create reactive resources that wrap promises and provide loading states.
/**
* Creates a resource without a source signal
* @param fetcher - Function that returns data or a promise
* @param options - Configuration options for the resource
* @returns Resource tuple with data accessor and actions
*/
function createResource<T, R = unknown>(
fetcher: ResourceFetcher<true, T, R>,
options?: ResourceOptions<T, true>
): ResourceReturn<T, R>;
/**
* Creates a resource with a source signal that triggers refetching
* @param source - Source signal that triggers refetching when it changes
* @param fetcher - Function that fetches data based on source value
* @param options - Configuration options for the resource
* @returns Resource tuple with data accessor and actions
*/
function createResource<T, S, R = unknown>(
source: ResourceSource<S>,
fetcher: ResourceFetcher<S, T, R>,
options?: ResourceOptions<T, S>
): ResourceReturn<T, R>;
type ResourceSource<S> = S | false | null | undefined | (() => S | false | null | undefined);
type ResourceFetcher<S, T, R = unknown> = (
source: S,
info: ResourceFetcherInfo<T, R>
) => T | Promise<T>;
interface ResourceFetcherInfo<T, R = unknown> {
value: T | undefined;
refetching: R | boolean;
}
interface ResourceOptions<T, S = unknown> {
initialValue?: T;
name?: string;
deferStream?: boolean;
ssrLoadFrom?: "initial" | "server";
storage?: (init: T | undefined) => [Accessor<T | undefined>, Setter<T | undefined>];
onHydrated?: (k: S | undefined, info: { value: T | undefined }) => void;
}Usage Examples:
import { createResource, createSignal, Show, Suspense } from "solid-js";
// Simple resource without source
const [data] = createResource(async () => {
const response = await fetch("/api/users");
return response.json();
});
// Resource with source signal
function UserProfile() {
const [userId, setUserId] = createSignal<number | null>(null);
const [user] = createResource(userId, async (id) => {
if (!id) return null;
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error("User not found");
return response.json();
});
return (
<div>
<input
type="number"
placeholder="Enter user ID"
onInput={(e) => setUserId(parseInt(e.target.value) || null)}
/>
<Show when={userId()}>
<Suspense fallback={<div>Loading user...</div>}>
<Show when={user()} fallback={<div>User not found</div>}>
{(userData) => (
<div>
<h2>{userData.name}</h2>
<p>Email: {userData.email}</p>
</div>
)}
</Show>
</Suspense>
</Show>
</div>
);
}Resources provide various states and actions for managing async data.
type ResourceReturn<T, R = unknown> = [
resource: Resource<T>,
actions: {
mutate: Setter<T | undefined>;
refetch: (info?: R) => T | Promise<T> | undefined | null;
}
];
type Resource<T> = Accessor<T | undefined> & {
state: "unresolved" | "pending" | "ready" | "refreshing" | "errored";
loading: boolean;
error: any;
latest: T | undefined;
};Usage Examples:
import { createResource, createSignal, Show, ErrorBoundary } from "solid-js";
function DataManager() {
const [refreshTrigger, setRefreshTrigger] = createSignal(0);
const [data, { mutate, refetch }] = createResource(
refreshTrigger,
async () => {
console.log("Fetching data...");
const response = await fetch("/api/data");
if (!response.ok) throw new Error("Failed to fetch");
return response.json();
},
{ initialValue: [] }
);
return (
<div>
<div class="toolbar">
<button
onClick={() => refetch()}
disabled={data.loading}
>
{data.loading ? "Refetching..." : "Refetch"}
</button>
<button
onClick={() => setRefreshTrigger(prev => prev + 1)}
>
Trigger Refresh
</button>
<button
onClick={() => mutate(prev => [...(prev || []), { id: Date.now(), name: "New Item" }])}
>
Add Item (Optimistic)
</button>
</div>
<div class="status">
<p>State: {data.state}</p>
<p>Loading: {data.loading ? "Yes" : "No"}</p>
<Show when={data.error}>
<p class="error">Error: {data.error.message}</p>
</Show>
</div>
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
<Show when={data()} fallback={<div>No data</div>}>
{(items) => (
<ul>
<For each={items}>
{(item) => <li>{item.name}</li>}
</For>
</ul>
)}
</Show>
</ErrorBoundary>
</div>
);
}Resources integrate seamlessly with Suspense for declarative loading states.
/**
* Tracks all resources inside a component and renders a fallback until they are all resolved
* @param props - Suspense component props
* @returns JSX element with loading state management
*/
function Suspense(props: {
fallback?: JSX.Element;
children: JSX.Element;
}): JSX.Element;Usage Examples:
import { createResource, Suspense, ErrorBoundary, For } from "solid-js";
function App() {
const [users] = createResource(() => fetchUsers());
const [posts] = createResource(() => fetchPosts());
return (
<div>
<h1>My App</h1>
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
<Suspense fallback={<div>Loading application data...</div>}>
<div class="content">
<section>
<h2>Users</h2>
<For each={users()}>
{(user) => <div class="user">{user.name}</div>}
</For>
</section>
<section>
<h2>Posts</h2>
<For each={posts()}>
{(post) => <div class="post">{post.title}</div>}
</For>
</section>
</div>
</Suspense>
</ErrorBoundary>
</div>
);
}
async function fetchUsers() {
await new Promise(resolve => setTimeout(resolve, 1000));
return [{ id: 1, name: "John" }, { id: 2, name: "Jane" }];
}
async function fetchPosts() {
await new Promise(resolve => setTimeout(resolve, 1500));
return [{ id: 1, title: "Post 1" }, { id: 2, title: "Post 2" }];
}Handle complex async scenarios with advanced resource patterns.
Usage Examples:
import { createResource, createSignal, createMemo, batch } from "solid-js";
// Dependent resources
function UserDashboard() {
const [userId, setUserId] = createSignal(1);
const [user] = createResource(userId, async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
});
// Posts depend on user data
const [posts] = createResource(
() => user()?.id,
async (userId) => {
if (!userId) return [];
const response = await fetch(`/api/users/${userId}/posts`);
return response.json();
}
);
return (
<Suspense fallback={<div>Loading dashboard...</div>}>
<div>
<Show when={user()}>
{(userData) => (
<div>
<h1>{userData.name}'s Dashboard</h1>
<Suspense fallback={<div>Loading posts...</div>}>
<For each={posts()}>
{(post) => <div class="post">{post.title}</div>}
</For>
</Suspense>
</div>
)}
</Show>
</div>
</Suspense>
);
}
// Resource with caching
function CachedDataExample() {
const cache = new Map();
const [cacheKey, setCacheKey] = createSignal("default");
const [data] = createResource(
cacheKey,
async (key) => {
if (cache.has(key)) {
console.log("Cache hit for", key);
return cache.get(key);
}
console.log("Fetching", key);
const response = await fetch(`/api/data/${key}`);
const result = await response.json();
cache.set(key, result);
return result;
}
);
return (
<div>
<select onChange={(e) => setCacheKey(e.target.value)}>
<option value="default">Default</option>
<option value="users">Users</option>
<option value="posts">Posts</option>
</select>
<Suspense fallback={<div>Loading {cacheKey()}...</div>}>
<pre>{JSON.stringify(data(), null, 2)}</pre>
</Suspense>
</div>
);
}
// Resource with retry logic
function RetryableResource() {
const [retryCount, setRetryCount] = createSignal(0);
const [data, { refetch }] = createResource(
retryCount,
async (count) => {
console.log(`Attempt ${count + 1}`);
// Simulate random failures
if (Math.random() < 0.7) {
throw new Error(`Failed on attempt ${count + 1}`);
}
return { message: `Success on attempt ${count + 1}!` };
}
);
const retry = () => {
setRetryCount(prev => prev + 1);
};
return (
<div>
<ErrorBoundary
fallback={(err, reset) => (
<div class="error">
<p>Error: {err.message}</p>
<button onClick={() => { reset(); retry(); }}>
Retry (Attempt {retryCount() + 2})
</button>
</div>
)}
>
<Suspense fallback={<div>Loading (Attempt {retryCount() + 1})...</div>}>
<Show when={data()}>
{(result) => (
<div class="success">
<p>{result.message}</p>
<button onClick={retry}>Try Again</button>
</div>
)}
</Show>
</Suspense>
</ErrorBoundary>
</div>
);
}Resources support server-side rendering with hydration capabilities.
// Resource with SSR support
const [data] = createResource(
() => fetchData(),
{
ssrLoadFrom: "server", // Load from server on SSR
deferStream: true, // Defer streaming until resolved
onHydrated: (key, info) => {
console.log("Resource hydrated", key, info);
}
}
);
// Custom storage for persistence
const [persistedData] = createResource(
() => fetchData(),
{
storage: (init) => {
// Custom storage implementation
const [value, setValue] = createSignal(init);
// Save to localStorage when value changes
createEffect(() => {
const current = value();
if (current !== undefined) {
localStorage.setItem("cached-data", JSON.stringify(current));
}
});
// Load from localStorage on mount
onMount(() => {
const stored = localStorage.getItem("cached-data");
if (stored) {
setValue(JSON.parse(stored));
}
});
return [value, setValue];
}
}
);type Resource<T> = Accessor<T | undefined> & {
state: "unresolved" | "pending" | "ready" | "refreshing" | "errored";
loading: boolean;
error: any;
latest: T | undefined;
};
type ResourceActions<T, R = unknown> = {
mutate: Setter<T>;
refetch: (info?: R) => T | Promise<T> | undefined | null;
};
type ResourceReturn<T, R = unknown> = [Resource<T>, ResourceActions<T | undefined, R>];
type InitializedResource<T> = Accessor<T> & {
state: "ready" | "refreshing" | "errored";
loading: boolean;
error: any;
latest: T;
};
type InitializedResourceReturn<T, R = unknown> = [
resource: InitializedResource<T>,
actions: {
mutate: Setter<T>;
refetch: (info?: R) => T | Promise<T> | undefined | null;
}
];
interface InitializedResourceOptions<T, S = unknown> extends ResourceOptions<T, S> {
initialValue: T;
}type ResourceFetcher<S, T> = (
source: S,
info: ResourceFetcherInfo<T>
) => T | Promise<T>;
interface ResourceFetcherInfo<T> {
value: T | undefined;
refetching: boolean | unknown;
}
type ResourceSource<S> = S | false | null | (() => S | false | null);Install with Tessl CLI
npx tessl i tessl/npm-solid-js