DEV Community

Cover image for Svelte without callbacks
spencer kelly
spencer kelly

Posted on

Svelte without callbacks

I've been doing front-end interviews this summer, which usually involve moving variables between React components. It's something I'm terrible at.

For years, jQuery applications were denounced for causing 'spaghetti code' - where variables were scattered throughout different callbacks. It made reasoning about the application tricky.

React was welcomed and celebrated for making state/life-cycle explicit. It's really been helpful. Moving variables between components though, feels like it was always awkward, and seems to get more convoluted every year.

This is what drew me to Svelte - you can change a variable from-a-distance in a spooky way. It seems to get around the need for all the spaghetti:

<!-- Parent.svelte -->
<script>
  import Child from './child.svelte'
  let str = 'init'
  setTimeout(() => {
    str = 'changed'
  }, 2500)
</script>
<div>
  <Child {str} />
</div>

<!-- Child.svelte -->
<script>
  export let str = ''
</script>
<div>
  child: <b>{$str}</b>
</div>
Enter fullscreen mode Exit fullscreen mode

Here, the child's HTML updates automatically. It's actually quite a remarkable thing. It feels like half the code is missing.


There's no callbacks ...?

I'll admit, I do sometimes get stuck on control-flow issues in Svelte. There can be some puzzlers. I sometimes find myself throwing-around callbacks when I shouldn't.

Consider this example:

<script>
  let value = 'foo'
</script>
<div>
  <input bind:value />
  magical variable: <b>{value}</b>
</div>
Enter fullscreen mode Exit fullscreen mode

This variable is 'hot' - it's wired-up both-ways. If you look at the source, there's getElementById's and addEventListener's - the stuff you've normally been writing yourself.


There's still no callbacks?

When a parent passes a variable to a child, the child will automatically listen for changes. The parent will only listen to the child if you use bind:

<!-- one-way (down) -->
<Child str={str} />
<!-- both-ways -->
<Child bind:str />
Enter fullscreen mode Exit fullscreen mode

A grandchild can change a variable using nested binds -

prop ➔ bind:prop ➔ bind:prop
Enter fullscreen mode Exit fullscreen mode

A sibling can change a variable using two binds -

prop ➔ [bind:prop, bind:prop]
Enter fullscreen mode Exit fullscreen mode

In both cases, things are updated between all the components automatically, as though it were a spreadsheet.


Ok - this bind thing is crazy. It's automating a big-chunk of web-development. It's automating the chunk that sucks. I'm feeling it.

Arguably, bind also resolves a lot of those Redux job-interview questions that I mess up.


But what about callbacks though?

I'm still a bit confused about this, admittedly.

Svelte has a way that you can declare a variable with $: instead of let - and it enters the spooky dependency-graph - where it knows when variables change somehow.

I don't fully get it, but it seems really powerful:

<!-- Parent.svelte -->
<script>
  import Child from './Child.svelte'
  let str = 'init'
  // (fancy-part!)
  $: strLen = () => {
    return str.length
  }
</script>
<div>
  <Child bind:str />
  {strLen()}
</div>

<!-- Child.svelte -->
<script>
  export let str = ''
  setTimeout(() => {
    str = 'child-changed'
  }, 2000)
</script>
Enter fullscreen mode Exit fullscreen mode

The function will re-fire after the child component changes the variable - and then the DOM will magically update, too

I mean, that's a callback. I just don't have to pass it around anymore. Using this $: thing, you can listen for changes, whenever you want to - from the top, the bottom, or in a side component. No sweat.


Maybe there are callbacks

These three aspects -

  1. the 'hot' props (going down)
  2. the 'bind' concept (going up)
  3. the $: thing (anywhere)

They seem to be a complete interface. Maybe this is the interface we want to have for public libraries, and open-source components. I can't tell.

There seems to be no gospel about what a Svelte component interface ought to look like. A lot of libraries accept props, and callbacks for each prop that changes. This 'events-up' callback style is cool with me too, I guess.

Oh - there is also the Store concept. This is a observable sub/pup thing. I used stores a lot until I figured-out I didn't need them as much. They are more explicit and traditional about the variable updating.

Maybe Svelte components should accept writable stores as props? That would look something like this:

<!-- Parent.svelte -->
<script>
  import { writable } from 'svelte/store'
  import Child from './Child.svelte'
  let str = writable('init')
  str.subscribe(wowChanged => {
    // is this a callback?
  })
</script>
<div>
  <Child {str} />
</div>

<!-- Child.svelte -->
<script>
  export let str
  setTimeout(() => {
    $str = 'child-changed' //change store
  }, 2000)
</script>
Enter fullscreen mode Exit fullscreen mode

I guess that's cool too.

Oh, and there's a Context concept too, which avoids having to pass props down all-together. That seems cool.

Oh, and there's a ton of people adding redux back to svelte (for some reason). That seems cool.

The svelte developers are also building this galaxy-brain Multi-Page-App thing called Sapper which routes data around components presumably, too. Cool.

It's all cool. Svelte is a really fun, and flexible library to use. I hope this message-passing stuff becomes clearer, as more people jump in. I'd like to not have to think about it anymore.

Top comments (0)