Guides

Fetching Data

Edit this page

For most modern web applications, data fetching is a common task. Solid has a built-in utility, createResource , that was created to simplify data fetching.


What is createResource ?

createResource is a specialized signal designed specifically for managing asynchronous data fetching. It wraps around the async operations, providing a way to handle various states: loading, success, and error.

This function is non-blocking, meaning that createResource guarantees that the application remains responsive, even during the retrieval of information. Because of this, common pitfalls of traditional async handling, such as unresponsive UIs during data fetching can be avoided.


Using createResource

createResource requires a function that returns a promise as its argument. Upon the call, createResource returns a signal which has reactive properties like loading, error, latest, etc. These properties can be used to conditionally render JSX based on the current reactive state.

The fetcher function that is created makes a call to get a user, which is then passed in as an argument to createResource.

The signal returned from the createResource provides the properties that help can assist with conditional rendering based on the current reactive state:

  • state: The current status of the operation (unresolved, pending, ready, refreshing, or errored).
  • loading: Indicates that the operation is currently in progress via a boolean.
  • error: If the operation fails for any reason, this property will contain information about this error. It may be a string with an error message, or an object with more detailed information.
  • latest: The most recent data or result returned from the operation.

When there is a change in the source signal, an internal fetch process is triggered to retrieve new data based on this change.

import { createSignal, createResource, Switch, Match, Show } from "solid-js";
const fetchUser = async (id) =>
{
const response = await fetch(`https://swapi.dev/api/people/${id}/`);
return response.json();
}
function App() {
const [userId, setUserId] = createSignal();
const [user] = createResource(userId, fetchUser);
return (
<div>
<input
type="number"
min="1"
placeholder="Enter Numeric Id"
onInput={(e) => setUserId(e.currentTarget.value)}
/>
<Show when={user.loading}>
<p>Loading...</p>
</Show>
<Switch>
<Match when={user.error}>
<span>Error: {error()}</span>
</Match>
<Match when={user()}>
<div>{JSON.stringify(user())}</div>
</Match>
</Switch>
</div>
);
}

Whenever the signal value, userId, changes, the internal fetch method fetchUser gets triggered. The properties of the user resource allow for conditional rendering based on the different states of the fetch process.

The Switch/Match construct provides one way to manage these conditions. When the fetch succeeds and user data is retrieved, the user() condition becomes active, and its related block executes. However, if there's an error while fetching, the user.error block becomes true, leading to its corresponding Match block being shown.

In addition to the error property, the loading property offers a way to display a loading state to the user during the fetch operation.


Calling multiple async events

Although you can use createResource independently, Solid provides an alternative method for synchronizing the display of multiple asynchronous events. Suspense is a component in Solid designed to act as a boundary. It allows you to display a fallback placeholder while waiting for all asynchronous events to resolve, preventing the display of partially loaded content:

import { createSignal, createResource, Switch, Match, Suspense } from "solid-js";
const fetchUser = async (id) => {
const response = await fetch(`https://swapi.dev/api/people/${id}/`);
return response.json();
}
function App() {
const [userId, setUserId] = createSignal();
const [user] = createResource(userId, fetchUser);
return (
<div>
<input
type="number"
min="1"
placeholder="Enter Numeric Id"
onInput={(e) => setUserId(e.currentTarget.value)}
/>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Match when={user.error}>
<span>Error: {error()}</span>
</Match>
<Match when={user()}>
<div>{JSON.stringify(user())}</div>
</Match>
</Switch>
</Suspense>
</div>
);
}

Suspense has the ability to identify asynchronous reads within its descendants and act accordingly. This feature helps to remove any intermediate components that may otherwise be displayed during partial loading states. Additionally, you can nest as many components as needed within Suspense but only the closest ancestor will switch to the fallback state when a loading state is detected.


Dynamic data handling

With the second output of createResource, there are 2 powerful methods designed to enhance and simplify some complex aspects of data management:

mutate

In situations where immediate feedback or responsiveness is important, the mutate method offers "optimistic mutations." These mutations provide instant feedback, even while background processes, such as server confirmations, are still in progress.

This functionality is particularly valuable in applications like task lists. For example, when users input a new task and click the Add button, the list will refresh immediately, regardless of the ongoing data communication with the server.

import { createResource } from 'solid-js';
function TodoList() {
const [tasks, { mutate }] = createResource(fetchTasksFromServer);
// ..
return (
<>
<ul>
{tasks().map(task => (
<li key={task.id}>{task.name}</li>
))}
</ul>
<button onClick={() => {
mutate((todos) => [...todos, "do new task"]) // add todo for user
// make a call to send to database
}}>Add Task</button>
</>
);
}

refetch

When real-time feedback is necessary, the refetch method can be used to reload the current query regardless of any changes. This method can be particularly useful when data is constantly evolving, such as with real-time financial applications.

import { createResource, onCleanUp } from 'solid-js';
function StockPriceTicker() {
const [prices, { refetch }] = createResource(fetchStockPrices);
const timer = setInterval(() => {
refetch()
}, 1000);
onCleanUp(() => clearInterval(timer))
}
Report an issue with this page