Fetching Data in Svelte

I always find it frustrating when I’m learning a new framework and I get to the point where I have to depart from my existing knowledge and principles in order to learn the framework’s way of thinking. Frameworks should exist for developers and so it was refreshing to discover that Svelte is mostly unopinionated when it comes to fetching server data. This means that developers can focus on creating high-quality applications instead of learning “the Svelte way” of fetching data.

The Tech

Because Svelte doesn’t have an out of the box solution for fetching data, it is possible to use almost any existing library to fetch data in your Svelte component. Even though almost everything is possible, fetch and XMLHTTPRequest are the easiest ways to fetch data in your Svelte component. In this article, I will examine specifically how we can use fetch to load data from the The Star Wars API, a publicly accessible API for Star Wars data.

Code Along

You can find working examples of all the code in this article on github: https://github.com/davidturissini/svelte-data-demo

Clone the repo and then run npm install && npm run dev. The app will be running at http://localhost:5000.

After reading this article you will be able to:

  • Load server data in your Svelte component
  • Load server data inside Svelte’s onMount lifecycle hook
  • Display a loading screen while data is being loaded
  • Load data on-demand from a user interaction

This article only covers fetching data for your Svelte components. If you are interested to learn more about Svelte and state management, be sure to follow me to get notified when that article gets published!

Using Fetch with Svelte

Code on Github

The easiest way to use fetch in your Svelte component is to simply invoke fetch directly in your component’s <script> tag. You’ll recall that Svelte’s reactivity model works by referencing a let variable directly in your component’s HTML. Whenever the variable gets a new value, Svelte will automatically re-render that new value.

With a modified approach, we can use this same functionality to render the contents of a fetch response. Instead of immediately assigning a value to our let variable, we instead dispatch a fetch request. We then populate our variable with values from our response once it settles. Our new values will then automatically be rendered into our HTML and become visible to the user.

For example, to display Luke Skywalker’s name in a Svelte component, we can create the variable characterName and then make a fetch call to https://swapi.dev/api/people/1. After our response is settled, we can then assign character.name to characterName. Since characterName is referenced in our HTML, Svelte will render the value for us. Fairly simple!

<script>
	let characterName;

	fetch('https://swapi.dev/api/people/1')
		.then((response) => response.json())
		.then((character) => {
			characterName = character.name;
		})
</script>
<main>
	{characterName}
</main>

This approach is not limited to just fetch. If we wanted to, we could create a Redux subscription and update characterName whenever we are passed a new value. We could also create a GraphQL subscription and follow the same pattern. As long as we can update variables that are in our HTML, Svelte will continue rendering the latest data no matter how we received those values.

Component onMount

Code on Github

Executing fetch in your <script> tag works well if you know that your component will always run in the browser. If it is even remotely possible that your component will be server rendered, we need to find a different approach. The biggest drawback with invoking fetch directly in your <script> that fetch will also be invoked when your component is rendered on the server. This could lead to some noticeable performance problems, both for your users and your servers.

We can improve our approach above by invoking our fetch call inside of Svelte’s onMount lifecycle hook. With the exception of onDelete, Svelte lifecycle hooks are never invoked on the server, so putting our fetch call inside of an onDelte hook guarantees that we will only call our APIs when the component is rendered in the browser. This will reduce your server load because we are only making our fetch call once the component is rendered in the browser. It also reduces time to serve because our server code does not have to wait for our data to settle before sending something back to the user.

<script>
    import { onMount } from 'svelte';
	let characterName;

    onMount(async () => {
        const response = await fetch('https://swapi.dev/api/people/1');
        const character = await response.json();
        characterName = character.name;
    })
</script>
<main>
	{characterName}
</main>

Handle Loading States

Code on Github

Even if we use onMount to fetch server data, we aren’t quite giving our users the best possible experience. Because characterName is not initialized with a default value, Svelte will render text "undefined" while our app fetches our data. Not ideal! We could avoid this by giving characterName some default value that is displayed while we fetch our data. That approach would work, and it would definitely be a better experience, but I think using an if-else conditional in our HTML to add a spinner would be an even better user experience. This approach is pretty powerful because there is no limit to what you can display while data is being fetched. It can be some simple text or it can be a complex Svelte component.

<script>
	import { onMount } from 'svelte';
	let characterName;

    onMount(async () => {
        const response = await fetch('https://swapi.dev/api/people/1');
        const character = await response.json();
        characterName = character.name;
    });
</script>

<main>
    {#if characterName === undefined}
        Loading Character Name...
    {:else}
        {characterName}
    {/if}
</main>

Lazy HTTP Requests

Code on Github

Invoking our fetch call inside of onMount means that every time our component mounts, we are going to make a server request. This is not always the correct behavior. Sometimes, we might want to wait for our users to give us a signal that they are ready for some data to be loaded. In this case, we can give our users some sort of UI, like a button, to control when our fetch call is invoked.

Instead of invoking our fetch call directly in onMount, we can make our fetch request lazy by moving it inside of a function that can be used as an event handler.

Making our fetch request lazy is a nice performance win. It reduces our server load and perceived user performance because we are not consuming memory or server resources with data that our user may never use. It also exposes an assumption that we made in our code. Up until now, all of our code samples have assumed that we are either making an HTTP request or the HTTP request has settled. Making our fetch lazy means that it is possible for our code to be idle. In our case, our idle state is just a period of time before the initial fetch request is triggered. In this state, we don’t need to show a loading indicator and we don’t yet have data to show the user so we need to update our code to handle this new behavior. There are many approaches that we could use, but the easiest way is to simply move characterName and loading into a tuple. We can then update our HTML conditional to not show our loading screen if loadig is false AND characterName is not present.

<script>
	let data = {
        characterName: undefined,
        loading: false,
    };

    async function loadData() {
        data.loading = true;
        const response = await fetch('https://swapi.dev/api/people/1')
        const character = await response.json();
        data = {
            characterName: character.name,
            loading: false,
        };
    }
</script>

<main>
    <button on:click={loadData}>Load Data</button>
    {#if data.loading === true}
        Loading Character Name...
    {:else if data.characterName !== undefined}
        {data.characterName}
    {/if}
</main>

Now our component waits for our user to click on our <button> before making an HTTP request. This is also a good pattern for making create, update, or delete server calls. We certainly wouldn’t want our component to be mutating data every time it loads!

Conclusion

Svelte is very flexible when it comes to fetching data for your application. By and large, you bring whichever tools you are comfortable with and you don’t need to reinvent the wheel to render your data. The easiest approach to loading data is by using fetch in our <script> tag but the most robust way to fetch data is to use onMount. Svelte also makes it easy to render a loading screen while our data is being fetched and to make our fetch requests lazy which improves our application’s overall performance. If there are any other tips or suggestions, feel free to leave them in the comments below!

Don’t forget to FOLLOW if you want to get more Svelte tutorials and deep dives as soon as they are published!