Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Loading data for routes

The post page needs the post's data, and the list page needs the list. Solid Router can load them for you: query describes a request and caches the result, createAsync reads it in a component, and the route's preload attribute starts loading before the page even renders.

Query functions

Collect the API requests in one file, src/api/posts.js:

// src/api/posts.js
import { query } from '@solidjs/router';
 
const API = 'https://jsonplaceholder.typicode.com';
 
export const getPosts = query(async () => {
  const res = await fetch(`${API}/posts?_limit=10`);
  return res.json();
}, 'posts');
 
export const getPost = query(async (id) => {
  const res = await fetch(`${API}/posts/${id}`);
  return res.json();
}, 'post');

The second argument of query is a name for the cache. Calling getPost('7') again within a short time returns the already loaded result instead of a new request.

Reading in a component

// src/pages/Posts.jsx
import { For } from 'solid-js';
import { A, createAsync } from '@solidjs/router';
import { getPosts } from '../api/posts';
 
export default function Posts() {
  const posts = createAsync(() => getPosts());
 
  return (
    <ul>
      <For each={posts()}>
        {(post) => (
          <li>
            <A href={`/posts/${post.id}`}>{post.title}</A>
          </li>
        )}
      </For>
    </ul>
  );
}

createAsync returns a getter function, like a signal. At http://localhost:3000/posts a list of ten posts from the server appears.

The post page

// src/pages/Post.jsx
import { Show } from 'solid-js';
import { useParams, createAsync } from '@solidjs/router';
import { getPost } from '../api/posts';
 
export default function Post() {
  const params = useParams();
  const post = createAsync(() => getPost(params.id));
 
  return (
    <Show when={post()}>
      <article>
        <h1>{post().title}</h1>
        <p>{post().body}</p>
      </article>
    </Show>
  );
}

The function inside createAsync reads params.id, so the request runs again when you move to another post. At /posts/1 you see the title and text of the first post.

Preloading

// src/index.jsx
import { getPosts, getPost } from './api/posts';
 
<Route path="/posts" component={Posts} preload={() => getPosts()} />
<Route path="/posts/:id" component={Post} preload={({ params }) => getPost(params.id)} />

preload is called when you navigate to the route and even when the mouse hovers over an A link. By the time the component calls the same function, the data is already in the cache.

Loading and errors

While data is on its way, createAsync behaves like a resource: wrap {props.children} in the App layout in Suspense with a fallback, and in ErrorBoundary if you like — then loading and errors of every page are handled in one place.

ToolJob
query(fn, 'name')Describe a request and cache it
createAsync(() => ...)Read the data in a component
preload on RouteStart loading early

Common mistake

Calling getPost(params.id) right in the component body and waiting for it with await. The component body is synchronous and runs once: the component will not wait for the data and will not update when the post changes. Always wrap the call in createAsync(() => ...).

Thanks for reading!

Donate to the site — we work hard for you.