Dylan Vann

Svelte Components Are Not Resilient

Common Svelte Patterns Encourage Non-Resilient Components

The term “resilient components” here is coming from Dan Abramov’s article Writing Resilient Components.

The principles he outlines are:

  1. Don’t stop the data flow.
    • Props and state can change, and components should handle those changes whenever they happen.
  2. Always be ready to render.
    • A component shouldn’t break because it’s rendered more or less often.
  3. No component is a singleton.
    • Even if a component is rendered just once, your design will improve if rendering twice doesn’t break it.
  4. Keep the local state isolated.
    • Think about which state is local to a particular UI representation — and don’t hoist that state higher than necessary.

The principle we’re discussing in relation to Svelte is “Don’t stop the data flow.”, otherwise phrased as “Props and state can change, and components should handle those changes whenever they happen.”.

Let’s start by writing a Svelte component. We want to write a component that fetches some photos from an API (this is taken from the onMount example in the Svelte docs).

PhotoGrid:

Now that we have a nice PhotoGrid component, let’s write a component for selecting an album:

PhotoGrid With AlbumSelector in Svelte (broken):

Beautiful, except that switching albums does not update the PhotoGrid. This is not the automatic reactivity we were promised by Svelte.

Why does it not work?

The answer is that onMount is not reactive, and so our component is not reactive. onMount does not rerun when our props change, so it does not fetch new data when the album prop is changed.

If we were to write our PhotoGrid component in React (with hooks) it would look something like this:

PhotoGrid with AlbumSelector in React (not broken):

This component is written to be almost the same as our Svelte version, and yet it works when we change albums, the photos for the selected album are fetched!

The key difference is that we use useEffect instead of onMount. Using the dependency array we tell React that the effect (fetching photos) should rerun when the album prop is changed. Even if we didn’t think of this dependency ourselves eslint-plugin-react-hooks would tell us about it.

When using React hooks there is no concept of onMount because the idea of only running some code on mount leads to writing non-resilient components, components that do one thing when they mount, and then don’t take prop changes into account.

Previously with React’s class component API we would need to use componentDidUpdate, React hooks improves upon this, it encourages writing resilient components from the start, and we can also write cleanup logic in the same useEffect call.

To fix our Svelte version you might think we could use beforeUpdate or afterUpdate, but these lifecycle functions are related to the DOM being updated, not to prop updates. We only want to rerun our fetching when the album prop is changed.

What is the best way to fix this then? I’m not sure what the best way is, but one way would be to implement useEffect for Svelte ourselves:

PhotoGrid with AlbumSelector in Svelte (fixed with useEffect):

With this code we use Svelte’s reactivity and a custom store to have our fetch rerun whenever the album prop is changed.

It works, I’m not sure this will work with SSR though, and the code is a bit verbose/convoluted. IMO Svelte should make something like useEffect part of the framework so that this could work better and be less verbose.

Conclusion

  • Our custom useEffect is not idiomatic Svelte.
  • Idiomatic Svelte uses onMount which results in non-resilient components.
  • Idiomatic React (Hooks) uses useEffect which results in resilient components, assuming you listen to the linter.

I do like the direction Svelte is heading but I think this is one area that could be improved.


Dylan Vann

Dylan Vann
Software developer living in Toronto 🇨🇦 (and sometimes NYC 🗽).
Focused on Node | GraphQL | React | React Native.

Join the Newsletter


Or follow me on Twitter for more stuff like this.
© 2020 Dylan Vann