|
2 | 2 | title: setContext and getContext
|
3 | 3 | ---
|
4 | 4 |
|
5 |
| -> This exercise doesn't currently work. You can switch to the old tutorial instead: https://svelte.dev/tutorial/context-api |
| 5 | +The context API provides a mechanism for components to 'talk' to each other without passing around data and functions as props, or dispatching lots of events. It's an advanced feature, but a useful one. In this exercise, we're going to recreate [Schotter](https://collections.vam.ac.uk/item/O221321/schotter-print-nees-georg/) by George Nees — one of the pioneers of generative art — using the context API. |
6 | 6 |
|
7 |
| -The context API provides a mechanism for components to 'talk' to each other without passing around data and functions as props, or dispatching lots of events. It's an advanced feature, but a useful one. |
| 7 | +Inside `Canvas.svelte`, there's an `addItem` function that adds an item to the canvas. We can make it available to components inside `<Canvas>`, like `<Square>`, with `setContext`: |
8 | 8 |
|
9 |
| -Take this example app using a [Mapbox GL](https://docs.mapbox.com/mapbox-gl-js/overview/) map. We'd like to display the markers, using the `<MapMarker>` component, but we don't want to have to pass around a reference to the underlying Mapbox instance as a prop on each component. |
| 9 | +```svelte |
| 10 | +/// file: Canvas.svelte |
| 11 | +<script> |
| 12 | + import { +++setContext+++, afterUpdate, onMount, tick } from 'svelte'; |
10 | 13 |
|
11 |
| -There are two halves to the context API — `setContext` and `getContext`. If a component calls `setContext(key, context)`, then any _child_ component can retrieve the context with `const context = getContext(key)`. |
| 14 | + // ... |
12 | 15 |
|
13 |
| -Let's set the context first. In `Map.svelte`, import `setContext` from `svelte` and `key` from `mapbox.js` and call `setContext`: |
| 16 | + onMount(() => { |
| 17 | + ctx = canvas.getContext('2d'); |
| 18 | + }); |
14 | 19 |
|
15 |
| -```js |
16 |
| -/// file: Map.svelte |
17 |
| -import { onMount, setContext } from 'svelte'; |
18 |
| -import { mapbox, key } from './mapbox.js'; |
19 |
| - |
20 |
| -setContext(key, { |
21 |
| - getMap: () => map |
22 |
| -}); |
23 |
| -``` |
24 |
| - |
25 |
| -The context object can be anything you like. Like [lifecycle functions](/tutorial/onmount), `setContext` and `getContext` must be called during component initialisation. Calling it afterwards - for example inside `onMount` - will throw an error. In this example, since `map` isn't created until the component has mounted, our context object contains a `getMap` function rather than `map` itself. |
| 20 | ++++ setContext('canvas', { |
| 21 | + addItem |
| 22 | + });+++ |
26 | 23 |
|
27 |
| -On the other side of the equation, in `MapMarker.svelte`, we can now get a reference to the Mapbox instance: |
| 24 | + function addItem(fn) {...} |
28 | 25 |
|
29 |
| -```js |
30 |
| -/// file: MapMarker.svelte |
31 |
| -import { getContext } from 'svelte'; |
32 |
| -import { mapbox, key } from './mapbox.js'; |
33 |
| - |
34 |
| -const { getMap } = getContext(key); |
35 |
| -const map = getMap(); |
| 26 | + function draw() {...} |
| 27 | +</script> |
36 | 28 | ```
|
37 | 29 |
|
38 |
| -The markers can now add themselves to the map. |
| 30 | +Inside child components, we can now get the context with, well, `getContext`: |
39 | 31 |
|
40 |
| -> A more finished version of `<MapMarker>` would also handle removal and prop changes, but we're only demonstrating context here. |
| 32 | +```svelte |
| 33 | +/// file: Square.svelte |
| 34 | +<script> |
| 35 | + +++import { getContext } from 'svelte';+++ |
41 | 36 |
|
42 |
| -## Context keys |
| 37 | + export let x; |
| 38 | + export let y; |
| 39 | + export let size; |
| 40 | + export let rotate; |
43 | 41 |
|
44 |
| -In `mapbox.js` you'll see this line: |
| 42 | + +++getContext('canvas').addItem(draw);+++ |
45 | 43 |
|
46 |
| -```js |
47 |
| -/// file: mapbox.js |
48 |
| -const key = {}; |
| 44 | + function draw(ctx) {...} |
| 45 | +</script> |
49 | 46 | ```
|
50 | 47 |
|
51 |
| -We can use anything as a key — we could do `setContext('mapbox', ...)` for example. The downside of using a string is that different component libraries might accidentally use the same one; using an object literal means the keys are guaranteed not to conflict in any circumstance (since an object only has referential equality to itself, i.e. `{} !== {}` whereas `"x" === "x"`), even when you have multiple different contexts operating across many component layers. |
| 48 | +So far, so... boring. Let's add some randomness to the grid: |
| 49 | + |
| 50 | +```svelte |
| 51 | +/// file: App.svelte |
| 52 | +<div class="container"> |
| 53 | + <Canvas width={800} height={1200}> |
| 54 | + {#each Array(12) as _, c} |
| 55 | + {#each Array(22) as _, r} |
| 56 | + <Square |
| 57 | + x={180 + c * 40+++ + jitter(r * 2)+++} |
| 58 | + y={180 + r * 40+++ + jitter(r * 2)+++} |
| 59 | + size={40} |
| 60 | + +++rotate={jitter(r * 0.05)}+++ |
| 61 | + /> |
| 62 | + {/each} |
| 63 | + {/each} |
| 64 | + </Canvas> |
| 65 | +</div> |
| 66 | +``` |
52 | 67 |
|
53 |
| -## Contexts vs. stores |
| 68 | +Like [lifecycle functions](/tutorial/onmount), `setContext` and `getContext` must be called during component initialisation. (The context key (`'canvas'` in this case) can be anything you like, including non-strings, which is useful for controlling who can access the context.) |
54 | 69 |
|
55 |
| -Contexts and stores seem similar. They differ in that stores are available to _any_ part of an app, while a context is only available to _a component and its descendants_. This can be helpful if you want to use several instances of a component without the state of one interfering with the state of the others. |
| 70 | +Your context object can include anything, including stores. This allows you to pass values that change over time to child components: |
56 | 71 |
|
57 |
| -In fact, you might use the two together. Since context is not reactive, values that change over time should be represented as stores: |
| 72 | +```js |
| 73 | +/// no-file |
| 74 | +// in a parent component |
| 75 | +import { setContext } from 'svelte'; |
| 76 | +import { writable } from 'svelte/store'; |
58 | 77 |
|
| 78 | +setContext('my-context', { |
| 79 | + count: writable(0) |
| 80 | +}); |
| 81 | +``` |
59 | 82 | ```js
|
60 | 83 | /// no-file
|
61 |
| -const { these, are, stores } = getContext(...); |
| 84 | +// in a child component |
| 85 | +import { getContext } from 'svelte'; |
| 86 | + |
| 87 | +const { count } = getContext('my-context'); |
| 88 | + |
| 89 | +$: console.log({ count }); |
62 | 90 | ```
|
0 commit comments