[S4SRD]S01E03 - Context Evolved (Updatable Context in Svelte)
Tags: javascript, react
Published: 2019-07-14 18:38:52


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 to update a value in svelte context. Ready your popcorn and welcome to:	


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

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

###Abstract

Last episode we created a hook to access context. Now we are looking into how to use context in a way we can update its value. 



The problem emerged from a statement about *getContext* and *setContext* functions:

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

I asked a similar question in stack overflow and **@Rich_Harris** was kind enough to point me in the right direction. Instead of just laying out the answer I decided to walk through the concept that would culminate in this idea. This way We get a better understanding of *why* instead of just focusing on *how*. Of course, if you don't want to journey this, just read the TL;DR 😉.



###TL;DR

Since the reference to a context value can't be updated. We need a way to access an *updatable* value in context. Svelte stores are perfect for this because they can be updated and observed. So basically, just use context with a store as its value.



### Can't Update The Reference, Now What ?!?

Let's start with our goal. We want to be able to **define a context value**, and then **update this value**, and finally **react to this and use the new value**. But... We can't update the context value reference after component initialization.  

Think of our context value as a *const*. In javascript we can't update the *const* reference, right? 

```javascript
(()=>{
	const a = {value:'initial'};
	a = {value: 'updated'} // ⭠ TypeError: Assignment to constant variable.
    console.log(a);
})()
```

But, if we have an *object* assigned to a *const* we can update any value (mutate) in it:

```javascript
(()=>{
	const a = {value:'initial'};
	a.value = 'updated'
    console.log(a); // outputs: {value: "updated"}
})()
```

### Isn't This Episode About svelte ??

Ok... How we apply this concept in svelte's context (I mean 🤔...  svelte context context 😕... You got it!). Try to follow along with the comments in this *nonpractical* example:

```html
<!-- App.svelte -->
<script>
	import ContextValue from './ContextValue.svelte';
	import {setContext, getContext} from 'svelte';
	setContext('value',{value:'inital'}); // ⭠ Create context
</script>
<ContextValue /> <!-- Import component that use the context -->

<!-- ContextValue.svelte -->
<script>
    import {getContext} from 'svelte';
    const contextValue = getContext('value'); // ⭠ Get context.
    
	function logContextValue(){ //⭠ Function to log current context value
		console.log(contextValue)
    }
    
    function updateContext(){ // ⭠ Function to "update" context
		myContext.value = 'updated'
	}
</script>
<button on:click={updateContext} >Update Context</button> <!-- ⭠ "Updates" context -->
<button on:click={logContextValue}>Log Context Value</button> <!-- ⭠ Log context -->
```



The expected idea is to: 
1 - click "Log Context Value" button  ⮕ outputs initial value

2 - click "Update Context" button;

3 - click "Log Context Value" button  ⮕ outputs updated value

And... It works!



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


### Still Messy


Yeah... Not so great yet. The logic is all over the place, and we didn't even create a reusable function for that (imagine using it in many components). We need several functions to make it work. It's messy. How about this?

```javascript
//smartContext.js
import {setContext, getContext} from 'svelte';

export function setSmartContext(contextObject){
	setContext('value',contextObject);
}

export function getSmartContext(){
	const ctx = getContext('value');
	return {
		get:()=>ctx,
		update: newValue => ctx.value = newValue
	}
}
```

Better... It's isolated in one module. We could use it like such:

```html
<!-- App.svelte -->
<script>
	import ContextValue from './ContextValue.svelte';
	import {setSmartContext} from './smartContext.js'
	setSmartContext({value:'inital'}); //⭠ Set a smartContext
</script>
<ContextValue />

<!-- ContextValue.svelte -->
<script>
    import {getSmartContext} from './smartContext.js';
		const smartContext = getSmartContext('value'); //⭠ get a smartContext
		function updateContext(){
			smartContext.update('updated') //⭠ updates smartContext
		}
		function logContextValue(){
			console.log(smartContext.get()) //⭠ Set smartContext value
		}
</script>
<button on:click={updateContext} >Update Context</button>
<button on:click={logContextValue}>Log Context Value</button>
```

Still... It only works for a single value. If we want 2 distinct context values, we would need to replicate our *smartContext.js* (not so smart...). 

### Making It More Reusable

Actually, if You're creative enough You could realize the *smartContext* is just an **object that updates a variable in its scope** *(or context)*. For that, it doesn't even need an *external context* if there's an **internal context** *(or scope)*. It turns out there's a great feature in javascript for this: Functions !!!! Look:

```javascript
//smartContext.js
export default (defaultValue)=>{
		let value = defaultValue; //⭠ scope value
		return {
			set: newValue=>{
				value=newValue //⭠ update scope value
			},
			get: ()=>value,//⭠ get scope value
		};
	};
```

Interesting... But this doesn't bring to the table all features a  svelte context has to offer. So, let's combine them and create 2 smartContexts.

```html
<!-- App.svelte -->
<script>
	import ContextValue from './ContextValue.svelte';
	import {setContext} from 'svelte' //⭠ import default svelte context
	import smartContext from './smartContext.js' // ⭠ import smartContext "builder"

    //⮦Set a context value to a smartContext
	setContext('value', smartContext('initial')) 
	//⮦Set another context value to a smartContext
	setContext('unused', smartContext('unused'))
</script>
<ContextValue />

<!-- ContextValue.svelte -->
<script>
	  import {getContext} from 'svelte';
	  const smartContext = getContext('value'); //⭠ get a smartContext
	  const getUnusedContext = getContext('unused');//⭠ get a smartContext
	  function updateContext(){
	    smartContext.update('updated')//⭠ update the smartContext
	  }
	  function logContextValue(){
	    console.log(smartContext.get())//⭠ get the smartContext value
	  }
</script>
<button on:click={updateContext} >Update Context</button>
<button on:click={logContextValue}>Log Context Value</button>
```

### Adding Reactiveness

That's a lot better now! And I know It might seem like a great round trip to get to the same place, but It's important to understand and split the concepts. Bear with me just a bit. So are we done? Not really. We need:

>to be able to **define a context value**, and then **update this value**, and finally **react to this and use the new value**

We are already **defining a context value** and **updating this value** but we are not **reacting to this update**. The only way to get the updated value so far is by executing an imperative action (hence, "click the button").  If we had this value displayed on *ContextValue.svelte*, it wouldn't be automatically updated. Let's try that:

```html
<!-- ContextValue.svelte -->
<script>
	  import {getContext} from 'svelte';
	  const smartContext = getContext('value'); //⭠ get a smartContext
	  const getUnusedContext = getContext('unused');//⭠ get a smartContext
	  function updateContext(){
	    smartContext.update('updated')//⭠ update the smartContext
	  }
	  function logContextValue(){
	    console.log(smartContext.get())//⭠ get the smartContext value
	  }
</script>
<button on:click={updateContext} >Update Context</button>
<button on:click={logContextValue}>Log Context Value</button>
```

And the result is:

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

### A Better SmartContext

The value is not automatically updated. It makes sense, why would it anyway? We need a way to **obverse** or to **subscribe** to this value updates. Before jumping to address this, let's consolidate what we need:

A way to **store**, **update**, a **subscribe** to a **scoped value**.

The scope, as we've seen, it's handled by svelte context using *getContext* and *setContext*.  Our *smartContext* already **stores** and **updates** the value, but isn't **observable**. svelte comes with a handy feature to help us out: **svelte store**. 

Stores in svelte do exactly that, so we can completely replace *smartContext* with it. First *App.svelte*

```html
<!-- App.svelte -->
<script>
	import ContextValue from './ContextValue.svelte';
	import {setContext} from 'svelte'; //⭠ import svelt context
	import { writable } from 'svelte/store'; //⭠ import svelt writable store
	
	let smartContext = writable('initial');//⭠ initialize store
	setContext('value',smartContext);//⭠ set context value as the store
</script>
<ContextValue />

```

At this point, we will observe to store updates and reacts to it by updating a component variable. It's a little different than the previous approach of accessing the store value. When the store value changes, so our variable value will.

```html
<!-- ContextValue.svelte -->
<script>
	  import {getContext,onMount} from 'svelte';
        //⮦ get svelt store(replaced our smartContext)
		let smartContext = getContext('value'); 
		let contextValue;//⭠ this variable will hold the store value (context value)
        //⮦ update our variable whenever the store value get updated
		onMount(()=>smartContext.subscribe(v=>contextValue = v))
    
		//⮦ Function to update store value
		function updateContext(){
			smartContext.update(()=>'updated')
		}
       //⮦ We don't need to access store value, just access our "synced" variable
		function logContextValue(){	
			console.log(contextValue)
		}
</script>
<h1>{contextValue}</h1> <!-- print out our variable value -->
<button on:click={updateContext} >Update Context</button>
<button on:click={logContextValue}>Log Context Value</button>
```

And the result:

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



There You go. Now we're talking!!



### Making it even better... Get me some sugar !

It works! Finally. Still too verbose though, don't You think? Stores, as a built-in feature of svelte comes with a *syntax sugar* we can use: *auto-subscriptions*. It works by just putting a dollar sign ($) before your store variable name. Simple as that! We just need to change our *ContextValue.svelte* component. Check it out:

```html
<!-- ContextValue.svelte -->
<script>
	  import {getContext,onMount} from 'svelte';
		let smartContext = getContext('value');
		function updateContext(){
			smartContext.update(()=>'updated')
		}
		function logContextValue(){	
			console.log($smartContext) //⭠ auto-subscribed value
		}
</script>
<h1>{$smartContext}</h1> <!-- //⭠ auto-subscribed value -->
<button on:click={updateContext} >Update Context</button>
<button on:click={logContextValue}>Log Context Value</button>
```

Now It's smaller and more concise. And We get the added bonus of having svelte unsubscribe from the store when the component gets destroyed. One small problem with the previous version of the code I omitted.



Things are starting to get interesting. I recommend taking a look at stores examples(<https://svelte.dev/examples#writable-stores>) and documentation(<https://svelte.dev/docs#writable>) from svelte official docs. It's extremely simple to use. 

I might add an episode or two on the subject. Who knows? Let me know if You think I'ts interesting!!

❕⚠️⚠️⚠️ Spoiler Alert ⚠️⚠️⚠️❕
I promise I'll get to HOC. Just a couple more things first!

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