DEV Community

loading...

Intro to Svelte Query

hyper63 profile image hyper63 Originally published at Medium on ・4 min read

Svelte is an exciting frontend framework that compiles away, which results in very small bundles shipped to the client with rich application functionality and features. In this post, I want to show case a new module that is being ported from React, called Svelte Query. Svelte Query is a module that gives you control of your server requests by managing all of the side effect edge cases for you.

Tutorial

twilson63/svelte-query-demo

Contribute to twilson63/svelte-query-demo development by creating an account on GitHub.

twilson63GitHub

Let’s create a demo Svelte App that uses Svelte Query to review movies, the app will search and list movies using pagination and allow the user to add movie reviews for movies they watched.

npx degit sveltejs/template query-demo
cd query-demo
yarn
yarn add -D tinro
yarn add @sveltestack/svelte-query
Enter fullscreen mode Exit fullscreen mode

Setup Svelte-Query Provider

In App.svelte

<script>
import { QueryClient, QueryClientProvider } from '@sveltestack/svelte-query'
import { Route } from 'tinro'
import Users from './Users.svelte'

const queryClient = new QueryClient()

</script>
<QueryClientProvider client={queryClient}>
  <Route path="/">
    <h1>Svelte Query Demo</h1>
    <div>
      <a href="/users">Users</a>
    </div>
  </Route>
  <Route path="/users">
    <Users />
  </Route>
</QueryClientProvider>
Enter fullscreen mode Exit fullscreen mode

Basics

We are using https://jsonplaceholder.typicode.com as our API service.

Create a new file src/Users.svelte

<script>
import {useQuery} from '@sveltestack/svelte-query'
const usersUrl = 'https://jsonplaceholder.typicode.com/users'
const queryResult = useQuery('users', () =>
  fetch(usersUrl).then(res => res.json())
)
</script>
<header>
  <a href="/">Home</a>
  <h1>Users</h1>
</header>
{#if $queryResult.isLoading}
  <span>Loading...</span>
{:else if $queryResult.error}
  <span>Error</span>
{:else}
<div>
  <ul>
    {#each $queryResult.data as user}
      <li>{user.username} - {user.email}</li>
    {/each}
  </ul>
</div>
{/if}
Enter fullscreen mode Exit fullscreen mode

You will notice, we are using the {#if} command to check for the status of the query and display the appropriate markup. When the query is successful, we use the data property with the {#each} command to loop over every result and render the markup to the screen. The useQuery hook accepts any function that returns a promise.

Pagination

One of the many benefits of the Svelte Query component, is the ability to do pagination. Let’s take a look:

Create a src/Posts.svelte file

touch src/Posts.svelte

<script>
  import { Query } from '@sveltestack/svelte-query'

  let page = 1
  let posts = []
</script>
<header>
  <h1>Posts</h1>
</header>

{#each posts as post}

{/each}

<span>Current Page: {page}</span>
<div>
<button on:click={() => null}>Previous</button>
<button on:click={() => null}>Next</button>
</div>
Enter fullscreen mode Exit fullscreen mode

Modify the App.svelte file to include the Posts.svelte file

<script>
import { QueryClient, QueryClientProvider } from '@sveltestack/svelte-query'
import { Route } from 'tinro'
import Users from './Users.svelte'

import Posts from './Posts.svelte'

const queryClient = new QueryClient()

</script>
<QueryClientProvider client={queryClient}>
  <Route path="/">
    <h1>Svelte Query Demo</h1>
    <div>
      <a href="/users">Users</a>
      <a href="/posts">Posts</a>
    </div>
  </Route>
  <Route path="/users">
    <Users />
  </Route>

  <Route path="/posts">
    <Posts />
  </Route>
</QueryClientProvider>
Enter fullscreen mode Exit fullscreen mode

Now in the Posts.svelte file, lets build our paginated query

<script>
    import { Query } from '@sveltestack/svelte-query'

  let page = 1

  const fetchPosts = (page = 1) => fetch(
    `https://jsonplaceholder.typicode.com/posts?_page=${page}`
  ).then(res => res.json())

  $: queryOptions = {
     queryKey: ['posts', page],
     queryFn: () => fetchPosts(page),
     keepPreviousData: true
  }

</script>
<header>
  <a href="/">Home</a>
  <h1>Posts</h1>
</header>
<Query options={queryOptions}>
  <div slot="query" let:queryResult>
    {#if queryResult.status === 'loading'}
      Loading...
    {:else if queryResult.status === 'error'}
      <span>Error: {queryResult.error.message}</span>
    {:else}
      {#each queryResult.data as post}
      <p>{post.title}</p>
      {/each}
    {/if}
  </div>
</Query>
<span>Current Page: {page}</span>
<div>
  <button disabled={page === 1} on:click={() => {
    if (page > 1) {
      page = page - 1
    }
  }}>Previous</button>
<button on:click={() => page = page + 1}>Next</button>
</div>
Enter fullscreen mode Exit fullscreen mode

In the script section we add our fetchPosts function, and we use the reactive symbol to basically watch the page variable. Every time the page variable changes we will get a new queryOptions object.

In the markup section, we add a Query component and set the prop options to our queryOptions object. The Query component uses slots, so we provide a child div with the slot named "query" and set a let directive to assign the queryResult variable so that we can access it within our slot implementation.

Finally, we modify the buttons below to increment and decrement the page variable, and BAM! ⚡ We have pagination! ⚡

Mutation

Not only do we need to list items, we need to send data to our services, with Svelte Query, we do this with a mutation hook.

Lets create a new file called src/Todos.svelte

<script>
import { useQuery, useMutation } from '@sveltestack/svelte-query'
const url = 'https://jsonplaceholder.typicode.com/todos'
</script>
<header>
  <a href="/">Home</a>
  <h1>Todos</h1>
</header>
<form>
  <input type="text" name="title" placeholder="todo" />
  <button type="submit">Submit</button>
</form>
<ul>
</ul>
Enter fullscreen mode Exit fullscreen mode

And lets add the Todos component to the App.svelte file

<script>
import { QueryClient, QueryClientProvider } from '@sveltestack/svelte-query'
import { Route } from 'tinro'
import Users from './Users.svelte'
import Todos from './Todos.svelte'
import Posts from './Posts.svelte'

const queryClient = new QueryClient()

</script>
<QueryClientProvider client={queryClient}>
  <Route path="/">
    <h1>Svelte Query Demo</h1>
    <div>
      <a href="/users">Users</a>
      <a href="/todos">Todos</a>
      <a href="/posts">Posts</a>
    </div>
  </Route>
  <Route path="/users">
    <Users />
  </Route>
  <Route path="/todos">
    <Todos />
  </Route>
  <Route path="/posts">
    <Posts />
  </Route>
</QueryClientProvider>
Enter fullscreen mode Exit fullscreen mode

Open the Todos.svelte and lets create add our query and mutation

<script>
import { useMutation } from '@sveltestack/svelte-query'
const url = 'https://jsonplaceholder.typicode.com/todos'
let title = ''

const mutation = useMutation(newTodo => 
  fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newTodo)
  }).then(res => res.json())

const onSubmit = (e) => {
  $mutation.mutate({ userId: 1, title })
  title = ''
}
</script>
<header>
  <a href="/">Home</a>
  <h1>Todos</h1>
</header>
{#if $mutation.isLoading}
  <p>Adding todo...</p>
{:else if $mutation.isError}
  <p>Error: {$mutation.error.message}</p>
{:else if $mutation.isSuccess}
  <p>Success!</p>
{:else}
<form on:submit|preventDefault={onSubmit}>
  <input bind:value={title} type="text" name="title" placeholder="todo" />
  <button type="submit">Submit</button>
</form>
{/if}
Enter fullscreen mode Exit fullscreen mode

We create our useMutation hook and give it a function that takes a todo object and returns a promise. Then when we call the mutate function on form submit, we can manage the states using the mutation store.

Discussion (1)

pic
Editor guide
Collapse
gevera profile image
Denis Donici

That's interesting