paint-brush
Context and Provider Pattern Using Vue 3 Composition APIby@maoberlehner
4,826 reads
4,826 reads

Context and Provider Pattern Using Vue 3 Composition API

by Markus OberlehnerMay 27th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

React Context API provides a way to share properties that are required by many components (e.g., user settings, UI theme) without having to pass a prop through every level of the tree (aka prop drilling) Although Vue.js does not provide the same abstraction out of the box, in this article, we’ll see that in Vue 3, we have all the tools we need to replicate the same functionality quickly. In this example, we look at how we can use this pattern to make certain information globally available everywhere in our application.

Company Mentioned

Mention Thumbnail
featured image - Context and Provider Pattern Using Vue 3 Composition API
Markus Oberlehner HackerNoon profile picture

The React Context API provides a way to share properties that are required by many components (e.g., user settings, UI theme) without having to pass a prop through every level of the tree (aka prop drilling). Although Vue.js does not provide the same abstraction out of the box, in this article, we’ll see that in Vue 3, we have all the tools we need to replicate the same functionality quickly.

User settings provider

In this example, we look at how we can use this pattern to make certain information globally available everywhere in our entire application.

The 

ProvideUserSettings
 component you see beneath, provides a reactive 
state
 with some default values and an 
update()
 function for setting properties on the 
state
 object.

// src/components/ProvideUserSettings.js 
import {
  provide,
  reactive,
  readonly,
  toRefs,
} from 'vue';

// We use symbols as a unique identifiers.
export const UserSettingsStateSymbol = Symbol('User settings provider state identifier');
export const UserSettingsUpdateSymbol = Symbol('User settings update provider identifier');

export default {
  setup() {
    const state = reactive({
      language: 'en',
      theme: 'light',
    });
    // Using `toRefs()` makes it possible to use
    // spreading in the consuming component.
    // Making the return value `readonly()` prevents
    // users from mutating global state.
    provide(UserSettingsStateSymbol, toRefs(readonly(state)));

    const update = (property, value) => {
      state[property] = value;
    };
    provide(UserSettingsUpdateSymbol, update);
  },
  render() {
    // Our provider component is a renderless component
    // it does not render any markup of its own.
    return this.$slots.default();
  },
};

Next we take a look at how we can use the

ProvideUserSettings
component in our application.

<!-- src/App.vue -->
<script>
import ProvideUserSettings from './components/ProvideUserSettings';

export default {
  name: 'App',
  components: {
    ProvideUserSettings,
  },
};
</script>

<template>
  <ProvideUserSettings>
    <div>
      <!-- ... -->
    </div>
  </ProvideUserSettings>
</template>

We probably need the settings in a lot of different components throughout our application. Because of that, it makes sense to put the provider at the root level inside of our 

App
 component.

So we now have access to the user settings from anywhere in our component tree.

<!-- src/components/ButtonPrimary.vue -->
<script>
import { inject } from 'vue';

import { UserSettingsStateSymbol } from './ProvideUserSettings';

export default {
  setup() {
    const { theme } = inject(UserSettingsStateSymbol);
    
    return { theme };
  },
};
</script>

<template>
  <ButtonBase
    :class="$style[`t-${theme}`]"
  >
    <slot/>
  </ButtonBase>
</template>

<style module>
.t-light { /* ... */ }

.t-dark { /* ... */ }
</style>

Above, we see how to consume the state of the injected context. In the following example, we explore how to update the state from any component in our application.

<!-- src/components/ThemeSwitcher.vue -->
<script>
import { inject } from 'vue';

import { UserSettingsUpdateSymbol } from './ProvideUserSettings';

export default {
  setup() {
    const updateUserSettings = inject(UserSettingsUpdateSymbol);
    const updateTheme = value => updateUserSettings('theme', value);
    
    return { updateTheme };
  },
};
</script>

<template>
  <div>
    <button @click="updateTheme('dark')">
      Enable darkmode
    </button>
    <button @click="updateTheme('light')">
      Enable lightmode
    </button>
  </div>
</template>

This time we inject the 

update()
 function with the
UserSettingsUpdateSymbol
.

We wrap the injected function in a new 

updateTheme()
 function which directly sets the 
theme
 property of our user settings object.

In theory, we could not wrap our state with 

readonly()
 and mutate it directly. But this can create a maintenance nightmare because it becomes tough to determine where we make changes to the (global) state.

When we click one of the two buttons, the user settings state is updated, and because it is a reactive object, all components which are using the injected user settings state are updated too.

Wrapping it up

Although Vue.js does not have the concept of Context built-in like React, as we’ve seen in this article, it is straightforward to implement something similar to that with Vue 3 

provide/inject
 ourselves.

Previously published at https://markus.oberlehner.net/blog/context-and-provider-pattern-with-the-vue-3-composition-api/