[S4SRD]S01E02 - Hooked Evolved (Contextful Hooks in Svelte)
Tags: javascript, react
Published: 2019-07-12 15:00:57

This is a series about applying some common React concepts and patterns in sveltejs.  This season is about hooks. Last episode We've set the objective of exploring hooks and how to implement some of its features in svelte (Check it out if You haven't so far). In this episode, I'll show how we make use of context inside "svelte hooks". Ready your popcorn and welcome to:	


<center><h2>🙃</h2></center>

<center><h2>Svelte For The Stubborn React Developer</h2></center>

###Abstract

We defined hooks as: 

A **function** to **extract behavior** that allows you to **react to lifecycle** and **access the state and context**.

This time We'll be focusing on the **"access the state and context"**. Mostly on the context part, because the state part is svelte is not really a big issue (I'll come back to that). All functionality You get using context can be achieved by using stores (the same thing can be said about React Context and Redux/Mobx Store). For me, context come to solve *"props drilling"* problem. Where You need to pass around information to a deeply nested child object.



###TL;DR

Sveltejs exports 2 funcions: *getContext* and *setContext*. The *getContext* function retrieves a context value that belongs to the **closest parent** component. Since *setContext* and *getContext* are just functions, they can be simply imported into *svelte hooks* and used as such. The component on which hooks are imported define the **closest parent**.



###A Note About State

Since svelte compiles your *svelte code* into javascript, the component state is just variables. You don't have to worry too much about the lifecycle when You think about the state. The scoped variable reference get "invalidated" any time some part of the code changes it, and when invalidated, other parts of the code *react* to this change. In React functional components You have to use *useState* hooks and such because React runtime might "recalculate" your component in many situations. When this happens everything on the scope of that component gets recalculated.



### What We Will Build

The first thing that pops into your head when You hear "context" is "themes", right? That's a very common use case, but I wanted to try something different. Context is great when there's something You have to pass around a lot between several components, especially when your app has a deeply nested structure. The app will have a simple structure (for simplicity sake), but imagine it has a really nested component tree. 



We'll build a very simple app to display clocks from different timezones. Our app structure will look something like this:

```html
<APP>
    <City> <!-- ⭠ SET Context A -->
        <Name></Name>
        <Date></Date> <!-- ⭠ USE Context B -->
        <Clock></Clock> <!-- ⭠ USE Context A -->
    </City>
    <City> <!-- ⭠ SET Context B -->
        <Name></Name>
        <Date></Date> <!-- ⭠ USE Context B -->
        <Clock></Clock> <!-- ⭠ USE Context B -->
    </City>
</APP>
```

As You can see in my detailed diagram above, the City component will set some context it's child Clock component will use.

#### Components Basic Structure

Let's start by creating the structure of our basic components, and then We gradually change them to implement what we want.

```html
<!-- Clock.svelte -->
<script>
    let time = null;
</script>
<div>
    <h3>{time}</h3>
</div>
<!-- Date.svelte -->
<script>
    let date = null;
</script>
<div>
    <h3>{date}</h3>
</div>
<!-- City.svelte -->
<script>
    import Clock from './Clock.svelte'
    import Date from './Date.svelte'
    export let name;
    export let timezone; //⭠ will be used in a minute
    export let format; //⭠ will be used in 2 minutes
</script>
<div>
    <div>{name}</div>
    <Date></Date>
    <Clock></Clock>
</div>
<!-- App.svelte -->
<script>
	import City from './components/City.svelte';
</script>
<h2>Cities</h2>
<City name="New York City" timezone="America/New_York"></City>
<City name="Rio de Janeiro" timezone="America/Sao_Paulo"></City>
```

So... The idea here is that *App.svelte* has 2 cities (New York and Rio de Janeiro) and each has it's own timezone (and format, but ignore that for now). On the *City.svelte* some context value will be set, and this value will then be used by *Date.svelte*  and *Clock.svelte*. 

Now this could be done directly on the three components, but this is not so great for one  basic reason:

**It makes the components tightly coupled.** This context logic would be scattered around these 3 components and if You have to change it for some reason, You would have to change everywhere (in a larger app this will not scale well).

We can do it better. If only we have learned in the last episode a way to **extract behavior** that allows you to **react to lifecycle** and **access the state and context**. 

Wait a minute... That's right. A hook!

 

#### Implementing the hook

Soooo... We know our hook has to be able to access context values defined in parent components. Good thing svelte has just the right tools: *getContext* and *setContext*, which are just functions and can be imported and used in any file (such as our hook file). The catch here is that You need to call them during component initialization, so **don't call them** inside *onMount, onDestroy, clickEvents, etc.* 

The *setContext(key, value)* defines a context value for the specified key. While *getContext(key)* returns the value for the key on the closest parent component. Our hook will be used by both: parent and child component, so it needs to export a way to set the context and to access the context. With that in mind here we go:

```javascript
//useTimezone.js
// SET context
export function setTimezone({timezone, format}) {
  if (timezone) setContext('contextTimeZone', timezone);
  if (format) setContext('contextTimeFormat', format);
}

```



Exported function *setTimezone* simply set 2 context variables (if passed): *contextTimeZone and contextTimeFormat*. The first will hold the desired timezone and the second the desired date format. They will be indirectly used by *Clock.svelte* and *Date.svelte*.

Great! Now we need a way for both functions to access these context variables and do something with it. Our hook's *heavy* logic (or shared behavior).

```javascript
//useTimezone.js
// SET context
export function setTimezone({timezone, format}) {
  if (timezone) setContext('contextTimeZone', timezone);
  if (format) setContext('contextTimeFormat', format);
}
//helper function
function getFormattedDate(format, options) {
  return new Intl.DateTimeFormat(format, options).format(new Date())
}

// ACCESS context and so something useful
export function getTime({onSecond, onDate}) {
  let interval;
  const timezone = getContext('contextTimeZone') || 'UTC';
  const format = getContext('contextTimeFormat') || 'default';
  if (onDate) onDate(getFormattedDate(format, timezone, {
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    timeZone: timezone
  }));
  onMount(() => {
    if (onSecond) {
      interval = setInterval(() => {
        console.log('onsecond::'+format);
        onSecond(
            getFormattedDate(format, {
              hour: 'numeric',
              minute: 'numeric',
              second: 'numeric',
              timeZone: timezone
            })
        )
      }, 200);
    }
    return () => interval && clearInterval(interval);
  })
}
```

Let's analyze what's going on here by parts as jack the ripper would.

Function *getFormattedDate* is just a helper to.. well... format the date. Lame!

Function *getTime* is much more interesting. function basic structure can represented as such:

```javascript
export function getTime({onSecond, onDate}) {
    //get context value (this is outside onMount)
    const timezone = getContext('contextTimeZone') || 'UTC';
....
    //call onDate callback passing the formated Date
    if (onDate) onDate(getFormattedDate(format, timezone, {
....   
    //register on components onMount a interval calling onSecond callback
    onMount(() => {
    if (onSecond) {
      interval = setInterval(() => {
....
	//register onDestroy event to clear interval (check last episode for details) 
	return () => interval && clearInterval(interval);
}
```

 Now a few things to notice:

- getContext calls happen outside onMount events
- onSecond and onDate callbacks could be retrieved from context, but for learning, It's best not to get overcomplicated. 

The important part is that *getContext* will look for the closest parent context relative to the component it gets imported into. Nice, but how can we use it?

####Hooking The Components

Our first order of business is to set the context at the *City.svelte* component, for that we'll receive the values as props:

```html
<!-- City.svelte -->
<script>
    import Clock from './Clock.svelte'
    import Date from './Date.svelte'
    import {setTimezone} from './useTimezone';
    export let name;
    export let timezone;
    export let format;
    setTimezone({timezone, format}); // ⭠ set context values
</script>
<div>
    <div>{name}</div>
    <Date></Date> <!-- ⭠ No props passed to the compoent -->
    <Clock></Clock> <!-- ⭠ No props passed to the compoent -->
</div>
```

and we need to pass the values as props in *App.svelte*

```html
<!-- App.svelte -->
<script>
	import City from './components/City.svelte';
</script>
<h2>Cities</h2>
<City name="New York City" timezone="America/New_York" format="en-US"></City>
<City name="Rio de Janeiro" timezone="America/Sao_Paulo" format="pt-BR"></City>
```

<small>* timezone values passed in a format understood by Intl (<https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat)></small>

Now a timezone and format are passed to each *City* instance, which sets them as context variables. This values now need to be consumed by *Date.svelte* and *Clock.svelte*

```html
<!-- Date.svelte -->
<script>
    import {getTime} from './useTimezone'
    let date = null;
    getTime({onDate: (newTime)=> date=newTime})
</script>
<div>
    <h3>{date}</h3>
</div>
```

```html
<!-- City.svelte -->
<script>
    import {getTime} from './useTimezone'
    let time = null;
    getTime({onSecond: (newTime)=> time=newTime})
</script>
<div>
    <h3>{time}</h3>
</div>
```

Both components set a variable (*date* and *time*), passes a callback to our Hook function to update its value.

With all in place all, our code is this:

```html
<!-- App.svelte -->
<script>
	import City from './City.svelte';
</script>
<h2>Cities</h2>
<City name="New York City" timezone="America/New_York" format="en-US"></City>
<City name="Rio de Janeiro" timezone="America/Sao_Paulo" format="pt-BR"></City>

<!-- City.svelte -->
<script>
    import Clock from './Clock.svelte'
    import Date from './Date.svelte'
    import {setTimezone} from './useTimezone';
    export let name;
    export let timezone;
    export let format;
    setTimezone({timezone, format});
</script>
<div>
    <div>{name}</div>
    <Date></Date>
    <Clock></Clock>
</div>

<!-- Date.svelte -->
<script>
    import {getTime} from './useTimezone'
    let date = null;
    getTime({onDate: (newTime)=> date=newTime})
</script>
<div>
    <h3>{date}</h3>
</div>

<!-- Clock.svelte -->
<script>
    import {getTime} from './useTimezone'
    let time = null;
    getTime({onSecond: (newTime)=> time=newTime})
</script>
<div>
    <h3>{time}</h3>
</div>
```

And the final result is:



![result-1](https://thepracticaldev.s3.amazonaws.com/i/rrts4zjjlj3zwg0q0opg.gif)

New York City Date is in 'MM/DD/YYYY' format as Rio de Janeiro is in 'DD/MM/YYYY', also, times are localized too.

####Grandpa's Context

In the example above, *Clock.svelte* and *Date.svelte* gets the context from the *City.svelte* component. But context is evaluated from the closest parent, this means we can also define the context on the *App.svelte*. To create something like a default value. Check it out: 

```html
<!-- App.svelte -->
<script>
	import City from './City.svelte';
	import {setTimezone} from './useTimezone';
	setTimezone({format:'en-US'}); // ⭠ set value in App context
</script>
<h2>Cities</h2>
<!-- USES App context format value -->
<City name="New York City" timezone="America/New_York"></City>
<City name="Philadelphia" timezone="America/New_York"></City>
<!-- OVERRIDES App context format value -->
<City name="Rio de Janeiro" timezone="America/Sao_Paulo" format="pt-BR"></City>
```

This way, We define some value in *App.svelte* context, so *New York* and *Philadelphia* uses it, and *Rio de Janeiro* overrides it because a new context (closer to the component) is defined inside *City.svelte* from the 'format' props passed.

So again in our detailed diagram, We have something like:

```html
<APP><!-- ⭠ SET APP context -->
    <City New York> <!-- ⭠ DO NOT set context -->
        <Name></Name>
        <Date></Date> <!-- ⭠ USE APP context -->
        <Clock></Clock> <!-- ⭠ USE APP context -->
    </City>
    <City Philadelphia> <!-- ⭠ DO NOT set context -->
        <Name></Name>
        <Date></Date> <!-- ⭠ USE APP context -->
        <Clock></Clock> <!-- ⭠ USE APP context -->
    </City>
    <City Rio de Janeiro> <!-- ⭠ SET Rio de Janeiro context -->
        <Name></Name>
        <Date></Date> <!-- ⭠ USE Rio de Janeiro context -->
        <Clock></Clock> <!-- ⭠ USE Rio de Janeiro context -->
    </City>
</APP>
```

Voilà!

![result-2](https://thepracticaldev.s3.amazonaws.com/i/5s0jzdo7atuai12hf01c.gif)
Great! Now We've mastered context hooks. One more detail. Rember I said:

> The catch here is that You need to call them during component initialization, so **don't call them** inside *onMount, onDestroy, clickEvents, etc.* 



Well, How can we update the context value then? 



See you in the next episode.