The full example code can be found here

The article introducing Effector with React can be found here

The React example code can be found here

Previously we looked at how Effector simplifies making asynchronous HTTP calls with React. I mentioned that Effector works natively with Svelte because they rely on the same primitive, Observables. The Svelte website claims to “bring reactivity to Javascript itself”, and you don’t need state management libraries anymore. Coincidentally, the excellent built-in reactivity makes integrating with external state management a breeze. We’re going to see how Svelte amplifies the declarative benefits of Effector.

In our React example, we prepended all our stores with $ as a convention. This convention lets us know at a glance which variables are stores. However, the $ character has special significance in Svelte. Because Svelte is a compiler, it can use $ as a hook to say I want to subscribe to this Observable and rerender when there is an update. This little bit of magic means we can’t start variable names with $, which is the only change from the React example. Otherwise, the code is the same.

import { createEvent, createEffect, createStore } from "effector";

export const fetchBeersFx = createEffect(async () => {
  const response = await fetch("https://api.punkapi.com/v2/beers");
  if (!response.ok) {
    throw Error(response.statusText);
  }
  return response.json();
});

export const beersError = createStore(null).on(
  fetchBeersFx.failData,
  (state, error) => error
);

export const beers = createStore([]).on(
  fetchBeersFx.doneData,
  (state, beers) => beers
);

export const selectedBeer = createStore(null);
export const selectBeer = createEvent();
selectedBeer.on(selectBeer, (state, beer) => beer);

In the App component, we set up our components to show the beer list, the selected beer and handle the loading and error states. We also trigger the call to fetch the beers with fetchBeersFx.

<script>
  import { fetchBeersFx } from "./store/Beers";
  import List from "./List.svelte";
  import Loading from "./Loading.svelte";
  import Error from "./Error.svelte";
  import Selected from "./Selected.svelte";

  fetchBeersFx();
</script>

<style>
  .App {
    font-family: sans-serif;
    text-align: center;
  }
  .Columns {
    display: flex;
  }
</style>

<main>
  <div class="App">
    <h1>Beer Browser</h1>
      <Loading />
      <Error />
      <section class="Columns">
        <List />
        <Selected />
      </section>
  </div>
</main>

We can see how $ subscriber makes pulling data into our views very easy and declarative in the following components.

List

<script>
  import { beers } from "./store/Beers";
  import ListItem from "./ListItem.svelte";
</script>

<style>
  .BeerList {
    list-style: none;
    width: 50%;
  }
</style>

<ul class="BeerList">
  {#each $beers as beer}
    <ListItem beer={beer}/>
  {/each}
</ul>

Loading

<script>
  import { fetchBeersFx } from "./store/Beers";

  export let pending = fetchBeersFx.pending;
</script>

{#if $pending}
  Loading...
{/if}

Error

<script>
  import { beersError } from "./store/Beers";
</script>

{#if $beersError}
  Whoops something went wrong
{/if}

Selected

<script>
  import { selectedBeer } from "./store/Beers";
</script>

<style>
  .Selected {
    width: 50%;
  }
</style>

<div class="Selected">
  {#if $selectedBeer}
    <p>{$selectedBeer.description}</p>
    <p>{$selectedBeer.abv} abv</p>
    <img height="200" src={$selectedBeer.image_url} alt={$selectedBeer.name} />
  {/if}
</div>

In the List component, we loop through the fetched beers using each pulling from the beers store. Similarly, the Loading and Error components pull from the beersError and fetchBeersFx.pending states. Finally, in the Selected component, we read the various attributes we’re interested in from the selectedBeer store. All these components update automatically, without the need for hooks like in Vue or React and because of Effector’s small stores only the components that need to update will update.

The combination of Svelte and Effector together makes a lot of sense. The interoperability enabled by Observables delivers on Svelte’s goal to bring reactivity to Javascript. The Svelte team probably didn’t know about Effector, but they could design the library’s reactivity to interoperate seamlessly based on the Observable interface. Its not just Effector, Svelte works with RXJS too.

Other library authors might argue that having full control of their reactive primitives allows them to make the best tools for their users. However, using a collection of tools based on web standards that work together by default is a big win that delivers more flexibility and adaptability. We avoid lock-in to one particular ecosystem. Should one library follow a course we’re not happy with, Angular JS to Angular being the classic example, we can find a replacement that fulfils the same contract. Once locked into one ecosystem, our only options are to live with the direction it pursues, even if we don’t agree with it, or undertake a rewrite of our app.

Suppose we separate concerns, letting our view library handle rendering our UI and user interaction and our state library manage and persist our data, interacting through the standard Observable primitive. In that case, we avoid the pitfalls associated with lock-in, and we can focus on adding features and fixing bugs rather than doing another rewrite.