DEV Community

Cover image for [S4SRD]S01E01 - Hooked On (using hooks in svelte)
Tiago Nobrega
Tiago Nobrega

Posted on

[S4SRD]S01E01 - Hooked On (using hooks in svelte)

I've met dev.to from @Rich_Harris twitter, so I decided to write my first post about @sveltejs. Not only that but how to reactfy Your svelte app (I'm sure Harris would love that, right ?). Not only that, it's a series.

Bear with me now and hold any impulse to close the browser window just yet and welcome to:

🙃

Svelte For The Stubborn React Developer

S01E01 - Hooked On (using hooks in svelte)

Abstract

Okay... Jokes apart, the idea here is to demonstrate how some concepts a React developer is familiar with, could be implemented in svelte. It's by no means a contest of who does it better (judgment-free zone). Also, the code presented is not battle tested, stressed out, military-grade techniques. It's just the way I found so far to implement things, feel free to leave a comment about anything.

TL;DR

Svelte lifecycle functions can be called from anywhere and isolated in isolated modules. It can then be imported to be used by as many components as You wish. They must be called during the component's initialization, but You don't have to worry about ordering and conditional call.

If You're interested in figuring out how to use state or context with a svelte hook, stay tuned for the next episode.

What's a hook anyway and What is it for?

Borrowing from @dan_abramov here:

You can’t extract behavior like “watch window size and update the state” or “animate a value over time” from a class component without restructuring your code or introducing an abstraction like Observables. Both approaches hurt the simplicity that we like about React.
Hooks solve exactly that problem. Hooks let you use React features (like state) from a function — by doing a single function call. React provides a few built-in Hooks exposing the “building blocks” of React: state, lifecycle, and context.>

So a hook is essentially a function to extract behavior that allows you to react to lifecycle and access the state and context.

How to achieve that in svelte?

Extracting behavior is what all functions do, that's the easy part. ✔️Done. But what about reacting to lifecycle?

Svelte exposes a few helper functions for that: OnMount, beforeUpdate, afterUpdate, onDestroy, tick. They work exactly the same way (except for tick - that creepy little guy). So, for simplicity reasons let's stick with onMount & onDestroy.

Now, let's imagine we want to log something anytime a component gets mounted or destroyed. First, let's do it for a single component:

<!-- componentA.svelte -->
<script>
import { onMount, onDestroy } from 'svelte';

onMount(()=>console.log('A component was mounted'))
onDestroy(()=>console.log('A component was destroyed'))
</script>

<h1>Hi, I'm component componentA.svelte</h1>

That's pretty simple, but it can get better. (and this is something it took me a while to find, although it's in the docs). From svelte docs:

If a function is returned from onMount, it will be called when the component is unmounted.>

And refactoring the code above:

<!-- componentA.svelte -->
<script>
import { onMount } from 'svelte';

onMount(()=>{
    console.log('A component was mounted')
    return ()=>console.log('A component was destroyed')//⬅️ onDestroy
})
</script>

<h1>Hi, I'm component componentA.svelte</h1>

Now, If we want to replicate this behavior to another component, in order to keep the code DRY, we need to extract it to a reusable module such as (You guessed right) a function. But how can an isolated function use onMount and onDestroy of the component? Or in other words, how to inject onMount and onDestroy into an isolated function?

My first idea was to pass it as arguments. But it turns out You don't need to do that. onMount and onDestroy are just functions. You can call them from anywhere. So our behavior function module can be implemented like this:

//useLogger.js
import { onMount } from 'svelte';
export default function useLogger(){
    onMount(()=>{
        console.log('A component was mounted')
        return ()=>console.log('A component was destroyed')//⬅️ onDestroy
    })
}

and used in all of our components like this:

<!-- componentA.svelte -->
<script>
import useLogger from './useLogger';
useLogger();
</script>
<h1>Hi, I'm component componentA.svelte</h1>

<!-- componentB.svelte -->
<script>
import useLogger from './useLogger';
useLogger();
</script>
<h1>Hi, I'm component componentB.svelte</h1>

And if we wish to make the logged message a little more personalized:

//useLogger.js
import { onMount } from 'svelte';
export default function useLogger(componentName='A component'){
    onMount(()=>{
        console.log(`${componentName} was mounted`)
        return ()=>console.log(`${componentName} was destroyed`)//⬅️ onDestroy
    })
}
<!-- componentA.svelte -->
<script>
import useLogger from './useLogger';
useLogger('Component A');
</script>
<h1>Hi, I'm component componentA.svelte</h1>

<!-- componentB.svelte -->
<script>
import useLogger from './useLogger';
useLogger('Component B');
</script>
<h1>Hi, I'm component componentB.svelte</h1>

Now it looks juicy! Just one point of attention: "It must be called during the component's initialization". So You can't use it on click handlers and such. The bright side is You don't have to worry about ordering or conditional calls for Your hooks.

That's it! We've just implemented a function to extract behavior that allows you to react to lifecyle. But what about the "access the state and context" part? That's a topic for S01E02. Stay tuned!

❕⚠️⚠️⚠️ Spoiler Alert ⚠️⚠️⚠️❕

I've heard some rumors that S02 is about HOC

❕⚠️⚠️⚠️ Spoiler Alert ⚠️⚠️⚠️❕

Latest comments (0)