Fetching Data
Our Bookshelf application is almost complete, but we live in a dynamic world.
Our applications are hardly ever self-contained.
This is why Solid includes the concept of resources out of the box.
Resources are special signals designed specifically to handle asynchronous loading and are created using the createResource
function.
Resources can be triggered by signals themselves. You'll usually pass createResource
two parameters: a signal and a data fetching function that relies on that signal:
tsx
const [data] = createResource(signal, dataFetchingFunction);
tsx
const [data] = createResource(signal, dataFetchingFunction);
The signal triggers the dataFetchingFunction: whenever it becomes a value other than null
, undefined
, or false
, the dataFetchingFunction
will be called, with that value as the first argument.
When our dataFetchingFunction
completes, it will update the value of data()
, which we can access like a normal signal. Additionally, data.loading
and data.error
properties will be available to us so we can react to the state of the data fetching.
Later, if the value of signal
changes again, dataFetchingFunction
will rerun again (as long as that value isn't null
, undefined
, or false
).
Fetching data for the bookshelf
In our Bookshelf application, we're not going to force our users to type the full book name and author of each book: we'll use an API to look up books.
Fortunately, OpenLibrary.org provides an API that we can use to look up books by their titles.
For example, https://openlibrary.org/search.json?q=Lord%20of%20the%20Rings
will perform a search for the book "Lord of the Rings" and we will be able to get the book's official title and author.
Let's first write a data fetching function called searchBooks
based on this API. The function will take a search term and return an array of matches from the OpenLibrary.org API:
tsx
export async function searchBooks(query) {if (query.trim() === "") return [];const response = await fetch(`https://openlibrary.org/search.json?q=${encodeURI(query)}`);const results = await response.json();return results.docs.slice(0, 10).map(({ title, author_name }) => ({title,author: author_name?.join(", "),}));}
tsx
export async function searchBooks(query) {if (query.trim() === "") return [];const response = await fetch(`https://openlibrary.org/search.json?q=${encodeURI(query)}`);const results = await response.json();return results.docs.slice(0, 10).map(({ title, author_name }) => ({title,author: author_name?.join(", "),}));}
This code fetches data from the OpenLibrary.org API, selects the first 10 results, and then transforms the results a bit to only return title
and author
properties for each result.
Let's set this data-fetching function aside for a moment so we can refactor our AddBooks.tsx
component. It currently consists of a form that lets users enter both the title and author of a book they want to add to their bookshelf.
Now that we know we'll be using the OpenLibrary.org API, we can simplify this form a bit: we're just going to ask users to search for a book by its title. The text the user is typing will be set in the input
signal. Once the user hits the Search
button, the current value of the input
signal will be stored in the query
signal. This will be the source signal for our resource.
tsx
import { createSignal } from "solid-js";export function AddBook(props) {const [input, setInput] = createSignal("");const [query, setQuery] = createSignal("");return (<form><div><label for="title">Search books</label><inputid="title"value={input()}onInput={(e) => {setInput(e.currentTarget.value);}}/></div><buttontype="submit"onClick={(e) => {e.preventDefault();setQuery(input());}}>Search</button></form>);}
tsx
import { createSignal } from "solid-js";export function AddBook(props) {const [input, setInput] = createSignal("");const [query, setQuery] = createSignal("");return (<form><div><label for="title">Search books</label><inputid="title"value={input()}onInput={(e) => {setInput(e.currentTarget.value);}}/></div><buttontype="submit"onClick={(e) => {e.preventDefault();setQuery(input());}}>Search</button></form>);}
Now that we have our source signal, query
, and our data-fetching function, searchBooks
, we can use createResource
to query the OpenLibrary.org API! As a reminder, createResource
will take the source signal and data-fetcher function as arguments:
tsx
const [data] = createResource(query, searchBooks);
tsx
const [data] = createResource(query, searchBooks);
Knowing that we'll have access to data.loading
when we're in a loading state, we can display "Searching..." when data is loading or a list of results when data is loaded. Let's use the <Show />
and <For />
control flow features we've learned.
tsx
const [data] = createResource(query, searchBooks);<Show when={!data.loading} fallback={<>Searching...</>}><ul><For each={data()}>{(book) => (<li>{book.title} by {book.author} <button>Add</button></li>)}</For></ul></Show>;
tsx
const [data] = createResource(query, searchBooks);<Show when={!data.loading} fallback={<>Searching...</>}><ul><For each={data()}>{(book) => (<li>{book.title} by {book.author} <button>Add</button></li>)}</For></ul></Show>;
Finally, we'll want to make sure the <button />
in each list item adds the item to our book list.
tsx
const [data] = createResource(query, searchBooks);<Show when={!data.loading} fallback={<>Searching...</>}><ul><For each={data()}>{(book) => (<li>{book.title} by {book.author}{" "}<buttonaria-label={`Add ${book.title} by ${book.author} to the bookshelf`}onClick={(e) => {e.preventDefault();props.setBooks((books) => [...books, book]);}}>Add</button></li>)}</For></ul></Show>;
tsx
const [data] = createResource(query, searchBooks);<Show when={!data.loading} fallback={<>Searching...</>}><ul><For each={data()}>{(book) => (<li>{book.title} by {book.author}{" "}<buttonaria-label={`Add ${book.title} by ${book.author} to the bookshelf`}onClick={(e) => {e.preventDefault();props.setBooks((books) => [...books, book]);}}>Add</button></li>)}</For></ul></Show>;
Accessibility note
We can include an aria-label
on our Add button to make the action taken by the button clear to screen readers. Solid will never get in the way of writing accessible code, but it also doesn't write it for you!
Finishing the app
Let's plug this resource fetching and display code into our AddBook
file, which will complete the application:
tsx
import { createSignal, JSX, createResource, For, Show } from "solid-js";import { searchBooks } from "./searchBooks";export function AddBook(props) {const [input, setInput] = createSignal("");const [query, setQuery] = createSignal("");const [data] = createResource(query, searchBooks);return (<><form><div><label for="title">Search books</label><inputid="title"value={input()}onInput={(e) => {setInput(e.currentTarget.value);}}/></div><buttontype="submit"onClick={(e) => {e.preventDefault();setQuery(input());}}>Search</button></form><Show when={!data.loading} fallback={<>Searching...</>}><ul><For each={data()}>{(book) => (<li>{book.title} by {book.author}{" "}<buttonaria-label={`Add ${book.title} by ${book.author} to the bookshelf`}onClick={(e) => {e.preventDefault();props.setBooks((books) => [...books, book]);}}>Add</button></li>)}</For></ul></Show></>);}
tsx
import { createSignal, JSX, createResource, For, Show } from "solid-js";import { searchBooks } from "./searchBooks";export function AddBook(props) {const [input, setInput] = createSignal("");const [query, setQuery] = createSignal("");const [data] = createResource(query, searchBooks);return (<><form><div><label for="title">Search books</label><inputid="title"value={input()}onInput={(e) => {setInput(e.currentTarget.value);}}/></div><buttontype="submit"onClick={(e) => {e.preventDefault();setQuery(input());}}>Search</button></form><Show when={!data.loading} fallback={<>Searching...</>}><ul><For each={data()}>{(book) => (<li>{book.title} by {book.author}{" "}<buttonaria-label={`Add ${book.title} by ${book.author} to the bookshelf`}onClick={(e) => {e.preventDefault();props.setBooks((books) => [...books, book]);}}>Add</button></li>)}</For></ul></Show></>);}
Solid's Bookshelf
My books
- Code Complete (Steve McConnell)
- The Hobbit (J.R.R. Tolkien)
- Living a Feminist Life (Sarah Ahmed)
Congratulations!
You just built your first Solid app! You now know how to build user interfaces, manage state, conditionally display information, and dynamically fetch content using Solid.