Skip to content

Resources ​

A Resource is a reactive abstraction for working with asynchronous values. Resources provide built-in loading and error states, making it easy to integrate async data into your reactive systems.

Key Characteristics ​

  • Async-first: Designed specifically for Promise-based operations
  • Loading states: Automatic loading signal
  • Error handling: Built-in error signal
  • Refetchable: Easy to trigger data refetching
  • Reactive sources: Can depend on other reactive values

Creating Resources ​

Standalone Resource ​

For async operations that don't depend on reactive values:

typescript
import { createResource } from '@signalis/core';

const postResource = createResource(() =>
  fetch('https://api.example.com/post').then((res) => res.json()),
);

// Check loading state
if (postResource.loading.value) {
  console.log('Loading...');
}

// Check for errors
if (postResource.error.value) {
  console.error('Error:', postResource.error.value);
}

// Access the data
if (postResource.value) {
  console.log('Post:', postResource.value);
}

// Refetch the data
postResource.refetch();

Resource with Source ​

For async operations that depend on reactive values. The resource will only fetch when the source is truthy, allowing you to delay fetching until you have the required data:

typescript
import { createSignal, createResource } from '@signalis/core';

const pageNumber = createSignal<number | null>(null);

const postResource = createResource(pageNumber, (page) =>
  fetch(`https://api.example.com/posts/${page}`).then((res) => res.json()),
);

// No request happens yet (pageNumber is null)

pageNumber.value = 1;
// Now the fetcher runs with page=1

pageNumber.value = 2;
// Fetcher runs again with page=2

API Reference ​

createResource<ValueType>(fetcher: () => Promise<ValueType>): Resource<ValueType> ​

Creates a standalone resource that fetches immediately.

Parameters:

  • fetcher: An async function that returns a Promise

Returns: A Resource<ValueType> with:

  • value: ValueType | undefined - The fetched data
  • last: ValueType | undefined - Previous value (for optimistic updates)
  • loading: Signal<boolean> - Loading state
  • error: Signal<unknown> - Error state
  • refetch(): Function to trigger a new fetch

createResource<SourceType, ValueType>(source: Signal<SourceType> | Derived<SourceType>, fetcher: (source: SourceType) => Promise<ValueType>): ResourceWithSource<ValueType, SourceType> ​

Creates a resource that depends on a reactive source value.

Parameters:

  • source: A signal or derived value to watch
  • fetcher: An async function that receives the source value

Returns: A ResourceWithSource<ValueType, SourceType> with the same properties as Resource

Note: The fetcher only runs when the source is truthy (not null, undefined, or false).

Resource State ​

A resource exposes four pieces of state:

value: ValueType | undefined ​

The most recent successfully fetched value.

typescript
const data = createResource(() => fetchData());

if (data.value) {
  console.log('Data:', data.value);
}

last: ValueType | undefined ​

The previous value before the current fetch. Useful for showing stale data while fetching:

typescript
if (data.loading.value && data.last) {
  console.log('Showing old data:', data.last);
} else if (data.value) {
  console.log('Showing current data:', data.value);
}

loading: Signal<boolean> ​

Whether the resource is currently fetching.

typescript
if (data.loading.value) {
  showSpinner();
} else {
  hideSpinner();
}

error: Signal<unknown> ​

Contains any error thrown during fetching.

typescript
if (data.error.value) {
  console.error('Failed to fetch:', data.error.value);
}

Common Patterns ​

REST API Calls ​

typescript
const userId = createSignal<number | null>(null);

const userResource = createResource(userId, (id) =>
  fetch(`/api/users/${id}`).then((res) => res.json()),
);

// Later
userId.value = 123; // Triggers fetch

Pagination ​

typescript
const page = createSignal(1);
const pageSize = createSignal(10);

// Combine multiple signals using a derived value
const params = createDerived(() => ({
  page: page.value,
  pageSize: pageSize.value,
}));

const paginatedData = createResource(params, ({ page, pageSize }) =>
  fetch(`/api/items?page=${page}&pageSize=${pageSize}`).then((res) => res.json()),
);

const nextPage = () => page.value++;
const prevPage = () => page.value--;

Dependent Fetching ​

typescript
const userId = createSignal<number | null>(null);

const userResource = createResource(userId, (id) =>
  fetch(`/api/users/${id}`).then((res) => res.json()),
);

// Fetch user's posts once we have user data
const postsResource = createResource(
  createDerived(() => userResource.value?.id),
  (userId) => fetch(`/api/users/${userId}/posts`).then((res) => res.json()),
);

With Loading and Error UI ​

typescript
import { createEffect } from '@signalis/core';

const data = createResource(() => fetchData());
const { loading, error } = data;

createEffect(() => {
  if (loading.value) {
    renderLoading();
  } else if (error.value) {
    renderError(error.value);
  } else if (data.value) {
    renderData(data.value);
  }
});

Manual Refetch ​

Note: Only standalone resources (without sources) support manual refetching.

typescript
const data = createResource(() => fetchData());

// Trigger based on user action
button.addEventListener('click', () => {
  data.refetch();
});

// Trigger based on signal change
const refreshTrigger = createSignal();

createEffect(() => {
  refreshTrigger.value; // Create dependency
  data.refetch();
});

For resources with sources, you can trigger refetching by updating the source value:

typescript
const userId = createSignal(1);
const userResource = createResource(userId, fetchUser);

// Trigger refetch by updating the source
userId.value = 2; // This will refetch with the new user ID

Polling ​

typescript
const data = createResource(() => fetchData());

// Poll every 5 seconds
const interval = setInterval(() => {
  data.refetch();
}, 5000);

// Cleanup
clearInterval(interval);

Optimistic Updates ​

typescript
const itemId = createSignal(1);
const itemResource = createResource(itemId, (id) =>
  fetch(`/api/items/${id}`).then((res) => res.json()),
);

function updateItem(newData: any) {
  // Show optimistic update using last value
  if (itemResource.last) {
    renderItem({ ...itemResource.last, ...newData });
  }

  // Trigger actual update
  fetch(`/api/items/${itemId.value}`, {
    method: 'PATCH',
    body: JSON.stringify(newData),
  }).then(() => {
    itemResource.refetch();
  });
}

Error Handling ​

Resources automatically catch errors and store them in the error signal:

typescript
const data = createResource(async () => {
  const res = await fetch('/api/data');

  if (!res.ok) {
    throw new Error(`HTTP error! status: ${res.status}`);
  }

  return res.json();
});

createEffect(() => {
  if (data.error.value) {
    console.error('Failed to fetch:', data.error.value);
    showErrorMessage(data.error.value);
  }
});

Preventing Stale Updates ​

When a resource depends on a changing value, previous requests might complete after newer ones:

typescript
const searchQuery = createSignal('');

let currentRequest = 0;

const searchResource = createResource(searchQuery, async (query) => {
  const requestId = ++currentRequest;
  const results = await searchAPI(query);

  // Only return if this is still the latest request
  if (requestId === currentRequest) {
    return results;
  }
});

Gotchas ​

Source Must Be Truthy ​

Resources with sources only fetch when the source is truthy. This allows you to delay fetching until you have the data you need:

typescript
const userId = createSignal<number | null>(null);
const user = createResource(userId, fetchUser);

// No fetch happens (userId is null)

userId.value = 0; // Still no fetch! (0 is falsy)

userId.value = 1; // Now it fetches

This pattern is useful for:

  • Conditional fetching: Only fetch when you have required parameters
  • User-triggered loading: Wait for user input before making requests
  • Dependent data: Fetch data only after prerequisite data is available

Refetch Doesn't Reset Errors ​

You may want to manually clear errors before refetching:

typescript
function retryFetch() {
  data.error.value = undefined;
  data.refetch();
}

See Also ​

  • Signals - Understanding the underlying signal type
  • Effects - Responding to resource state changes
  • Derived - Creating complex source dependencies
  • Examples - Complete async data example

Released under the MIT License.