SolidJS createResource: Bind Async Fetching to a Signal
Use SolidJS createResource to tie fetch requests to reactive signals, with automatic loading states and abortion of stale requests.
08 Apr 2026, 04:46 UTC

Start with the signal, not the component
When a search input, route parameter, or selected ID changes, you usually need to fetch new data. In many frameworks this means restarting effects, managing cleanup, and guarding against responses that arrive out of order. SolidJS offers createResource for exactly this: it couples an async fetcher to one or more reactive sources and exposes loading, error, and value states without re-executing the component.
Useful takeaway: declare the resource once, pass a signal as its source, and let SolidJS abort the previous request whenever that signal changes. Only the nodes that read the resource update.
How createResource tracks dependencies
createResource accepts a source function or signal and an async fetcher. The fetcher receives the resolved source value as its first argument and an AbortSignal as its second. SolidJS tracks any signals read inside the source function. When a tracked signal changes, SolidJS aborts the in-flight request tied to the old value and starts a new fetcher. The resource accessor returns the current value synchronously, so templates can branch on resource.loading, resource.error, and resource() without triggering a full component re-render.
Key terms:
- Resource accessor: the function returned by createResource. Calling it returns the latest resolved value, or undefined while loading.
- AbortSignal: a browser signal passed to fetch so SolidJS can cancel a request when its source becomes stale.
Worked example: signal-driven search
This example assumes a SolidJS 1.x application. The query signal feeds createResource. Each keystroke updates the signal, which aborts the previous fetch and starts a new one.
import { createSignal, createResource, For } from 'solid-js';
export default function Search() {
const [query, setQuery] = createSignal('');
const [results] = createResource(
query,
async (searchTerm, { signal }) => {
if (!searchTerm) return [];
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', searchTerm);
const response = await fetch(url.toString(), { signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
);
return (
setQuery(e.currentTarget.value)}
placeholder='Search...'
/>
{results.loading() ? (
Loading results...
) : results.error() ? (
Error: {results.error()?.message}
) : (
{(item) => - {item.title}
}
)}
);
}
Where to run: paste this into a SolidJS component file in a local project and start the dev server, typically with npm run dev in the project directory. No special permissions are required.
Expected checks:
- Open the browser Network tab, type quickly into the input, and confirm earlier requests show as canceled or aborted when a newer request starts.
- Confirm that only the result list updates while the input and surrounding markup stay mounted.
- Open the console and log
resultsduring a slow fetch to observe synchronous transitions between loading, error, and resolved states.
Risk: if the fetcher throws and you do not branch on results.error(), SolidJS may surface the error through its error boundary. Always handle the error state in the UI.
Reading the resource outside JSX
results() is a synchronous accessor, not a promise. Do not await it inside an event handler. If you need the current value outside a reactive context, wrap the read in createEffect or read it inside JSX, because tracking only happens while SolidJS is executing a reactive scope.
// Incorrect: results() is undefined during loading and is not a promise
const handleClick = async () => {
const data = await results(); // will not work as expected
};
// Correct: read inside a reactive scope or pass to another primitive
createEffect(() => {
const data = results();
console.log(data);
});
Manual refetch and optimistic updates
The resource tuple returns a second element with control methods. Call refetch() to re-run the fetcher with the current source value. Use mutate() to update the cached value optimistically without starting a new network request.
const [results, { refetch, mutate }] = createResource(query, fetcher);
// Re-run immediately, for example after a retry button click
Retry
// Optimistically insert a new item before the server responds
mutate((current) => [...(current ?? []), { title: 'New item', id: -1 }]);
Limits and common mistakes
- Missing reactive dependencies: If you read a non-reactive variable inside the fetcher instead of a tracked signal, the resource will not refetch when that variable changes. Keep all inputs that should trigger a refetch inside the source function.
- Side effects in the fetcher: Do not perform unrelated DOM updates or analytics calls inside the fetcher. Because SolidJS may re-run or abort the fetcher, side effects can run multiple times or against stale state.
- Reading outside reactive context: Calling
results()in a plain function or outside JSX creates no dependency, so the UI will not update when the resource resolves. - Dependent resource waterfalls: If resource B depends on the result of resource A, requests serialize. For independent sources, create separate resources and let them fetch in parallel.
Practical verification
- Add a temporary
console.log('fetch started', searchTerm)at the top of the fetcher and inspect the Network tab for canceled requests when changing the signal rapidly. - Set the browser to Slow 3G networking, type several characters, and confirm the displayed results correspond to the final request, not an intermediate one.
- Disable JavaScript error boundaries temporarily to confirm that a rejected fetch surfaces through
results.error()rather than crashing the component.
Version assumptions
This guide targets SolidJS 1.x, where createResource and its abort behavior are stable built-in primitives. No additional packages are required. If you are using an older major version, verify the resource API in that release's documentation before adopting this pattern.
No rollback procedure is needed because this change is limited to component source code and does not modify package state or build configuration. To revert, replace the resource with a manual createEffect and fetch implementation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.