Svelte Custom Stores for React Developers: Part 1

I started building a simple Svelte application that mirrors the application that I wrote for NSS students to learn React. It's called Nashville Kennels, and it deals with resources like employees, animals, locations, and customers.

The very first thing I wanted to do is get a list of animals rendered to the DOM after making a fetch() call to a local dev API. After reading the docs for Svelte, I discovered that their Custom Stores feature was the way to go. I wanted to keep the application state for animals maintained in one place, control access to the data, and make the data available to any other component in the system.

Getting Data into the Store

If you are a React developer, you are likely using Redux, or the Context API, to make data available to components throughout your application. Svelte custom stores are a good, simple replacement for it without the need for external dependencies or enforcing a parent/child relationship with your components.

This is akin to using custom Hooks that manage the state for each resource that your application uses.

For a Svelte store, I started with the following code. Note that it is a .js file, and not a .svelte file.

src/animals/AnimalStore.js

// Required import for a writable store of data
import { writable } from 'svelte/store';

/*
    Create store with initial empty array. Looks kinda like...
        const [animals, set] = useState([])
*/
const { subscribe, set } = writable([]);

// Export subscriptable data. Magic happens here.
export const animals = { subscribe }

export const getAnimals = async () => {
    const response = await fetch("http://localhost:8088/animals");
    const api_animals = await response.json();

    if (response.ok) {
        set(api_animals);  // Set the store data to API state
    }
};

Displaying the Data

A whole bunch of magic 🎩 happens with the Svelte compiler which allowed me to write an AnimalList component that started like this.

src/animals/AnimalList.svelte

<script>
  import { animals, getAnimals }  from  "./AnimalStore.js";
  getAnimals();  // Kick off getting animals into application state
</script>

<article class="animals">
  {#each $animals as animal}
    <section class="animal">
        <div class="animal--name"><h2>{ animal.name }</h2></div>
        <div class="animal--breed">{ animal.breed }</div>
    </section>
  {/each}
</article>

Rough Equivalent with React Context Provider

You would still import the state and the GET request function from the context provider, just like in the Svelte store above. In React, though, you would invoke the GET from the useEffect() hook.

import { AnimalContext } from "./AnimalProvider"

export const AnimalList = () => {
    const { animals, getAnimals } = useContext(AnimalContext)

	useEffect(() => {
	    getAnimals()
	}, [])

    return (
        <article class="animals">
        {
            animals.map(animal => `
                <section class="animal">
                    <div class="animal--name"><h2>{ animal.name }</h2></div>
                    <div class="animal--breed">{ animal.breed }</div>
                </section>
            `)
        }
        </article>
    )
}

The syntax between the two is actually quite similar. The Svelte code is just a bit more readable.

The Data

Before you see the end result, here's what the data looks like in my dev API service.

"animals": [
  { "id": 1, "name": "Doodles", "breed": "Poodle" },
  { "id": 2, "name": "Barney", "breed": "Boxer" },
  { "id": 3, "name": "Jax", "breed": "Dalmation" }
]

Kennel Component

Then I updated my main component to render the AnimalList component beneath a nice <h1> element.

src/Kennels.svelte

<script>
  import AnimalList from "./animals/AnimalList.svelte";
</script>

<style>
  /* Excluded for brevity */
</style>

<main>
  <h1>Nashville Kennels</h1>
  <AnimalList />
</main>

First Render

Next: Showing shared state across components >>