Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Data Fetching Guide #991

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
935a674
First draft
amirhhashemi Dec 21, 2024
d51d873
iteration #1
amirhhashemi Dec 22, 2024
bbf5def
iteration 2
amirhhashemi Dec 23, 2024
a73bf29
fix typo
amirhhashemi Dec 23, 2024
4b26cf6
Merge branch 'main' into add-data-fetching-guide
kodiakhq[bot] Dec 24, 2024
9e34c58
iteration 3
amirhhashemi Dec 28, 2024
ab07dd6
Merge branch 'main' into add-data-fetching-guide
kodiakhq[bot] Dec 30, 2024
554c737
Merge branch 'main' into add-data-fetching-guide
kodiakhq[bot] Dec 30, 2024
d41c75c
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
e444f1f
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
515c658
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
c8e62dd
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
96de833
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
9b12076
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
3591bf0
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
99a1797
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
fb1470c
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
49a0224
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
99ef369
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Dec 30, 2024
4de7347
iteration 4
amirhhashemi Dec 30, 2024
f6a6733
Merge branch 'main' into add-data-fetching-guide
kodiakhq[bot] Dec 30, 2024
96b62f7
iteration 5
amirhhashemi Jan 1, 2025
093fc06
fix typo
amirhhashemi Jan 1, 2025
9dd327d
Merge branch 'main' into add-data-fetching-guide
kodiakhq[bot] Jan 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/routes/solid-start/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"index.mdx",
"getting-started.mdx",
"building-your-application",
"advanced"
"advanced",
"guides"
]
}
218 changes: 218 additions & 0 deletions src/routes/solid-start/guides/data-fetching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
---
title: "Data Fetching"
---

This guide will walk you through the basics of data fetching in SolidStart, providing practical examples and best practices.

Here's a minimal example of data fetching in SolidStart:

```tsx title="src/routes/index.tsx"
import { For } from "solid-js";
import { query, createAsync } from "@solidjs/router";

const getPosts = query(async () => {
const posts = await fetch("https://my-api.com/posts");
return await posts.json();
}, "posts");

export default function Page() {
const posts = createAsync(() => getPosts());
return (
<ul>
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
</ul>
);
}
```

In this example, a [`query`](/solid-router/reference/data-apis/query) is created. In order to access it's data within the component, the [`createAsync`](/solid-router/reference/data-apis/create-async) primitive was used.

## Showing loading UI

To display a loading UI while data is being fetched, you can use [Solid `<Suspense>`](/reference/components/suspense):

1. Import `Suspense` from `solid-js`
2. Wrap the data rendering in `<Suspense>`
3. Provide a fallback component that will be displayed while the data is loading using the `fallback` prop

```tsx {13} {15} title="src/routes/index.tsx"
import { Suspense, For } from "solid-js";
import { query, createAsync } from "@solidjs/router";

const getPosts = query(async () => {
const posts = await fetch("https://my-api.com/posts");
return await posts.json();
}, "posts");

export default function Page() {
const posts = createAsync(() => getPosts());
return (
<ul>
<Suspense fallback={<div>Loading...</div>}>
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
</Suspense>
</ul>
);
}
```

## Handling errors

If the data fetching fails, a fallback UI can be displayed using an [`<ErrorBoundary>`](/reference/components/error-boundary):

1. Import `ErrorBoundary` from `solid-js`.
2. Wrap the data rendering in `<ErrorBoundary>`
3. Provide a fallback component that will be displayed if an error occurs using the `fallback` prop.

```tsx {13} {17} title="src/routes/index.tsx"
import { ErrorBoundary, Suspense, For } from "solid-js";
import { query, createAsync } from "@solidjs/router";

const getPosts = query(async () => {
const posts = await fetch("https://my-api.com/posts");
return await posts.json();
}, "posts");

export default function Page() {
const posts = createAsync(() => getPosts());
return (
<ul>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Suspense fallback={<div>Loading...</div>}>
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
</Suspense>
</ErrorBoundary>
</ul>
);
}
```

## Preloading data

Data fetching can be optimized during user navigation by preloading the data with the [`preload`](/solid-router/reference/preload-functions/preload) function:

1. Export a `route` object with a `preload` function
2. Run your query inside the `preload` function
3. Use the query as usual in your component

```tsx {9-11} title="src/routes/index.tsx"
import { ErrorBoundary } from "solid-js";
import { query, createAsync, type RouteDefinition } from "@solidjs/router";

const getPosts = query(async () => {
const posts = await fetch("https://my-api.com/posts");
return await posts.json();
}, "posts");

export const route = {
preload: () => getPosts(),
} satisfies RouteDefinition;

export default function Page() {
const post = createAsync(() => getPosts());
return (
<div>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<h1>{post().title}</h1>
</ErrorBoundary>
</div>
);
}
```
Comment on lines +90 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explain why preloading is even needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be more worthwhile having that in a the other aspects of the docs (like the core parts of "learn" or a "tutorial". This is geared more towards people asking "how to" instead of "why" and i worry if we add the "why" here, it'd be assumed the rest of it should have it as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense, we probably need a separate document for explaining the concepts and issues around data fetching + mutation


## Passing parameters to queries

When creating a query that accepts parameters, you can define your query function to take any number of arguments:

```tsx {9-10} {15} {19-20} title="src/routes/posts/[id]/index.tsx"
import { ErrorBoundary } from "solid-js";
import {
query,
createAsync,
useParams,
type RouteDefinition,
} from "@solidjs/router";

const getPost = query(async (id: string) => {
const post = await fetch(`https://my-api.com/posts/${id}`);
return await post.json();
}, "post");

export const route = {
preload: ({ params }) => getPost(params.id),
} satisfies RouteDefinition;

export default function Page() {
const params = useParams();
const post = createAsync(() => getPost(params.id));
return (
<div>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<h1>{post().title}</h1>
</ErrorBoundary>
</div>
);
}
```

In this example, [`useParams`](/solid-router/reference/primitives/use-params) was used to get the `id` path parameter. You can then pass it to the `getPost` query to get the data for that specific post.

## Using a database or an ORM

To safely interact with your database or ORM in a query, ensure the query is server-only to prevent exposing your database credentials to the client. You can do this by adding [`"use server"`](/solid-start/reference/server/use-server) as the first line of your query function:

```tsx {6-7} title="src/routes/index.tsx"
import { For, ErrorBoundary } from "solid-js";
import { query, createAsync, type RouteDefinition } from "@solidjs/router";
import { db } from "~/lib/db";

const getPosts = query(async () => {
"use server";
return await db.from("posts").select();
}, "posts");

export const route = {
preload: () => getPosts(),
} satisfies RouteDefinition;

export default function Page() {
const posts = createAsync(() => getPosts());
return (
<ul>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
</ErrorBoundary>
</ul>
);
}
```

## Fetching data on the client

When you only need to fetch data on the client side, you can use the [`createResource`](/reference/basic-reactivity/create-resource) primitive:

```tsx {4-7} title="src/routes/index.tsx"
import { createResource, ErrorBoundary, Suspense, For } from "solid-js";

export default function Page() {
const [posts] = createResource(async () => {
const posts = await fetch("https://my-api.com/posts");
return await posts.json();
});
return (
<ul>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Suspense fallback={<div>Loading...</div>}>
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
</Suspense>
</ErrorBoundary>
</ul>
);
}
```

You can [read more about `createResource` here](/reference/basic-reactivity/create-resource).
Comment on lines +192 to +214
Copy link
Contributor

@devagrawal09 devagrawal09 Jan 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So createAsync can still be used to fetch on the client side, and the only reason you'd use createResource instead is if you're not using solid-router, not if you're fetching on the client. This section seems to confuse those two things together.

I'd break this into two separate sections, one that shows fetching on the client with createAsync without "use server", and another that shows fetching using createResource on both client and server.

Nevermind, I just noticed that most of your createAsync snippets are fetching on the client already. The only correction I have is this section should be called "Fetching data without Solid Router" or something like that.

Copy link
Contributor Author

@amirhhashemi amirhhashemi Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the diataxis guide that we use as reference:

How-to guides must be written from the perspective of the user, not of the machinery. A how-to guide represents something that someone needs to get done. It’s defined in other words by the needs of a user. Every how-to guide should answer to a human project, in other words. It should show what the human needs to do, with the tools at hand, to obtain the result they need.

This is in strong contrast to common pattern for how-to guides that often prevails, in which how-to guides are defined by operations that can be performed with a tool or system. The problem with this latter pattern is that it offers little value to the user; it is not addressed to any need the user has. Instead, it’s focused on the tool, on taking the machinery through its motions.

Keeping each section action-oriented, focusing on a practical action that the user might want to do offers better value to the user. If the user wants to learn about a specific tool, it can go to the API Reference.

I believe the documentation should include a section discussing the pros and cons of using Solid.js versus Solid Router APIs for data fetching. Additionally, it would be helpful to clarify why some APIs are part of Solid Router while others belong to Solid.js. However, this page is not aimed at that type of content. Perhaps a tutorial on data fetching would be a more appropriate format for this discussion.

Copy link
Contributor Author

@amirhhashemi amirhhashemi Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my initial draft, I included a call-out that gave a little hint:

<Callout type="info">
  Note that `query` and `createAsync` are imported from `@solidjs/router`. By default, SolidStart uses [Solid Router](https://docs.solidjs.com/solid-router) under the hood. That means we can take advantage of all Solid Router data fetching primitives in SolidStart.
</Callout>

This may slightly disrupt the page's theme, but I think reintroducing a call-out like this could significantly help users navigate the material with greater clarity. At least until we add a comprehensive tutorial.


<Callout type="info" title="Advanced Data Handling">
For advanced features like automatic background re-fetching or infinite queries, you can use [Tanstack Query.](https://tanstack.com/query/latest/docs/framework/solid/overview)
</Callout>
Comment on lines +216 to +218
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the callout, however I still think it would be helpful to show an example snippet here that shows using tanstack query with "use server". Would love to get some more thoughts on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost wonder if we need a tutorial geared towards advanced data fetching. Like something that goes into the weeds of the more complicated parts people may run into?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think showcasing the usage of Tanstack Query with "use server" is interesting enough to include in this guide. But, my concern is that it might go too far. If we want to cover Tanstack Query properly, we should add:

  1. A basic example of createQuery (with and without "use server")
  2. A section about preloading data
  3. A section about error handling and loading UI
  4. A few examples of advanced things that Tanstack Query can do and SolidStart can't

It's too much for this page. I think it's better to leave it to the user to go through the Tanstack Query docs and figure things out. If there's any ambiguity regarding the use of "use server" with Tanstack Query, we should consider updating other sections of the documentation for clarity.

4 changes: 4 additions & 0 deletions src/routes/solid-start/guides/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Guides",
"pages": ["data-fetching.mdx"]
}
Loading