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
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
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
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
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!
Top comments (11)
Hi David,
Using a promise to fetch, you could use
await blocks
.Have a look at this: svelte.dev/tutorial/await-blocks
Agreed. {#await...then...catch} is the preferred way to do this, especially considering the fetch is already within an async function, which will return a promise.
I really appreciate that I don't need to learn "the svelty way" for fetching data. You explained it very well, David!
I just wanted to add that on more complex scenarios; It could be a good alternative to handle all the data fetching on services to reuse functions from different components.
Great post! We need more people sharing their experiences with Svelte!
Totally agree! In a more complex scenario, i think you want to completely separate data calls from components.
You could also just use
await
blocks offered by Svelte :)Very cool! I haven’t had a chance to use these yet
github.com/SvelteStack/svelte-query
Very good examples, Thanks for this article.
Thanks Pavel!
Nice one bro
Thanks Kinanee