Merge pull request #301 from MacFJA/pass-data

[New Recipe] Passing data between components
This commit is contained in:
MacFJA
2023-03-12 19:56:37 +01:00
committed by GitHub
2 changed files with 572 additions and 0 deletions

27
src/lib/Mermaid.svelte Normal file
View File

@@ -0,0 +1,27 @@
<script>
import { onMount } from 'svelte';
let graph = null;
let png = null;
onMount(() => {
png =
'https://mermaid.ink/img/' +
btoa(JSON.stringify({ code: graph.textContent }))
.replaceAll('+', '-')
.replaceAll('/', '_')
.replaceAll('=', '');
});
</script>
<pre bind:this={graph}><slot /></pre>
<img src={png} alt="Mermaid graph" />
<style>
pre {
display: none;
}
img {
margin: 0 auto;
}
</style>

View File

@@ -0,0 +1,545 @@
---
title: Passing data between components
layout: recipe
---
<script>
import Mermaid from "$lib/Mermaid.svelte"; import {HighlightSvelte} from "svelte-highlight";
import {HighlightAuto} from "svelte-highlight";
</script>
## Stores
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :-------: |
| &#9989; | &#9989; | &#9989; | &#9989; | Any |
<br />
Stores allow components to "react" when the content of the store is changed.
If the store is declared _"globally"_ (not in a components) any scripts can access it and interact with it.
<article>
<header>Globally declared store</header>
<aside>
<Mermaid>{`
flowchart BT
subgraph External file
Store["store"]
end
C1[Component1.svelte] <-- "$store" --> Store
C2[Component2.svelte] <-- "$store" --> Store
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Component1.svelte</summary>
<HighlightSvelte
code={`<script>
import { store } from "./my-store.ts"
</script>
<input bind:value={$store} />`}
/>
</details>
<details>
<summary>Component2.svelte</summary>
<HighlightSvelte
code={`<script>
import { store } from "./my-store.ts"
</script>
The store value is: <var>{$store}</var>`}
/>
</details>
<details>
<summary>my-store.ts</summary>
<HighlightAuto
code={`import { writable } from "svelte/store"
export const store = writable("hello")`}
/>
</details>
</nav>
</article>
<article>
<header>
<em>In component</em> declared store
</header>
<aside>
<Mermaid>{`
flowchart BT
C1[Component1.svelte] <-- "$store" --> C1
C2[Component2.svelte] <-- "$store" --> C1
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Component1.svelte</summary>
<HighlightSvelte
code={`<script context="module">
import { writable } from "svelte/store"
export const store = writable("hello")
</script>
<input bind:value={$store} />`}
/>
</details>
<details>
<summary>Component2.svelte</summary>
<HighlightSvelte
code={`<script>
import { store } from "./Component1.svelte"
</script>
The store value is: <var>{$store}</var>`}
/>
</details>
</nav>
<footer>
You are creating a hard/direct dependency between <code>Component1.svelte</code> and{' '}
<code>Component2.svelte</code>.<br />
<code>Component2.svelte</code> can't exist without <code>Component1.svelte</code>
</footer>
</article>
## Global variable
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :-------: |
| &#9989; | &#9989; | &#10060; | &#9989; | Any |
<br />
Similar to store, a global variable act as a central point of storage.
But global variable are not reactive, so you have to implement yourself a method to update the component.
<article>
<aside>
<Mermaid>{`
flowchart BT
subgraph External file
Store["myVar"]
end
C1[Component1.svelte] <-- "myVar" --> Store
C2[Component2.svelte] <-- "myVar" --> Store
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Component1.svelte</summary>
<HighlightSvelte code={`<script>
import { variable } from "./data.js"
</script>
In component1: <var>{variable}</var>`} />
</details>
<details>
<summary>Component2.svelte</summary>
<HighlightSvelte code={`<script>
import { variable } from "./data.js"
</script>
In component2: <var>{variable}</var>`} />
</details>
<details>
<summary>data.js</summary>
<HighlightAuto language="javascript" code={`export let variable = 2
export function updateVariable(value) {
variable = value
}`} />
</details>
<details>
<summary>App.svelte</summary>
<HighlightSvelte code={`<script>
import Component2 from "./Component2.svelte"
import Component1 from "./Component1.svelte"
import { variable, updateVariable } from "./data.js"
let v = variable
$: updateVariable(v)
</script>
<input bind:value={v} />
<hr />
<!-- Forcing reactivity -->
{#key v}
<Component1 />
<br>
<Component2 />
{/key}`} />
</details>
</nav>
</article>
## Svelte event
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :-----------------: |
| &#9989; | &#10060; | &#9989; | &#10060; | Child &rarr; Parent |
<br>
The data sharing only works in the direction child to parent.
<article>
<aside>
<Mermaid>{`
flowchart BT
Child[Child.svelte] -->|on:myEvent| Parent[Parent.svelte]
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte
code={`<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
let value = 'hello'
</script>
<input bind:value />
<button on:click={() => dispatch('validate', value}}>Validate data</button>
`}
/>
</details>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte
code={`<script>
import Child from "./Child.svelte"
</script>
<Child on:validate={(e) => console.log(e.detail)} />`}
/>
</details>
</nav>
</article>
## get/setContext
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :--------------------: |
| &#9989; | &#10060; | &#10060; | &#10060; | Parent &rarr; Children |
_(Can be bi-directional and reactive, see below)_
<br>
The data sharing only works in the direction parent to children (and children of children).
<article>
<header>Context with normal variable</header>
<aside>
<Mermaid>{`
flowchart TB
Parent[Parent.svelte]
Child[Child.svelte]
GrandChild[GrandChild.svelte]
Parent -.->|context| Child
Child -.->|inherit context| GrandChild
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte
code={`<script>
import { setContext } from "svelte"
import Child from "./Child.svelte"
setContext("name", "John Doe")
</script>
<Child />`}
/>
</details>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte
code={`<script>
import { getContext } from "svelte"
import GrandChild from "./GrandChild.svelte"
const name = getContext("name")
</script>
<p>Hello, my parent name is <var>{name}</var></p>
<GrandChild />`}
/>
</details>
<details>
<summary>GrandChild.svelte</summary>
<HighlightSvelte
code={`<script>
import { getContext } from "svelte"
const name = getContext("name")
</script>
<p>Hello, one of my parent have the name <var>{name}</var></p>`}
/>
</details>
</nav>
</article>
<article>
<header>
Context with store
<div><small>Bi-directional and reactive</small></div>
</header>
<aside>
<Mermaid>{`
flowchart TB
Parent[Parent.svelte]
Child[Child.svelte]
GrandChild[GrandChild.svelte]
Parent -.->|context| Child
Child -.->|inherit context| GrandChild
Child --> Parent
GrandChild --> Parent
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte code={`<script>
import { setContext } from "svelte"
import { writable } from "svelte/store"
import Child from "./Child.svelte"
const minAge = writable(80)
setContext("min-age", minAge)
</script>
<p>The youngest of the family is {$minAge} year old.</p>
<Child />`} />
</details>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte code={`<script>
import { getContext } from "svelte"
import GrandChild from "./GrandChild.svelte"
const minAge = getContext("min-age")
$minAge = 50 // Override the age (Parent is updated)
</script>
<GrandChild />`} />
</details>
<details>
<summary>GrandChild.svelte</summary>
<HighlightSvelte code={`<script>
import { getContext } from "svelte"
const age = getContext("min-age")
$age = 25 // Override the age (Parents are updated), initial value
</script>
<div>
<!-- The age can be edited, all parents will be updated thanks to the store contract -->
<label>I'm the youngest, and my age is: <input type="number" bind:value={$age} /></label>
</div>`} />
</details>
</nav>
</article>
## Props
### One-way binding (down)
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :-----------------: |
| &#9989; | &#10060; | &#9989; | &#10060; | Parent &rarr; Child |
<br>
The parent is providing the value to its child.
Changing the value in the parent will be reflected in the child.
<article>
<aside>
<Mermaid>{`
flowchart TB
Parent[Parent.svelte]
Child[Child.svelte]
Parent -->|"myVar=#123;myVar}"| Child
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte
code={`<script>
import Child from "./Child.svelte"
let name="Joe"
</script>
My child name is <input bind:value={name} />
<hr />
<Child {name} />`}
/>
</details>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte
code={`<script>
export let name="Rich"
</script>
My name is <var>{name}</var>`}
/>
</details>
</nav>
</article>
### Two-way binding
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :-----------------: |
| &#9989; | &#10060; | &#9989; | &#9989; | Parent &harr; Child |
<br>
The parent is providing a variable that can be written by the child.
Changes made in the parent will be reflected and the child, changes made in children will be send to its parent
<article>
<aside>
<Mermaid>{`
flowchart TB
Parent[Parent.svelte]
Child[Child.svelte]
Parent <-->|bind:myVar| Child
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte
code={`<script>
import Child from "./Child.svelte"
let name="Joe"
</script>
My child name is <input bind:value={name} />
<hr />
<Child bind:name={name} />`}
/>
</details>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte
code={`<script>
export let name="Rich"
</script>
My name is <input bind:value={name} />`}
/>
</details>
</nav>
</article>
### One-way binding (up)
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :-----------------: |
| &#9989; | &#10060; | &#9989; | &#10060; | Child &rarr; Parent |
<br>
The parent is providing a function that will update the parent scope from its children
<article>
<aside>
<Mermaid>{`
flowchart TB
Parent[Parent.svelte]
Child[Child.svelte]
Parent -->|"myFunc=#123;myFunc}"| Child
Child -->|"myFunc()"| Parent
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte
code={`<script>
import Child from "./Child.svelte"
let name="Joe"
function setName(value) {
name = value
}
</script>
My child name is <var>{name}</var>
<hr />
<Child set={setName} />`}
/>
</details>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte
code={`<script>
export let set
let myName
</script>
My name is <input bind:value={myName} />
<button on:click={() => set(myName)}>Send</button>`}
/>
</details>
</nav>
</article>
## DOM event
| In Component | In Javascript/TypeScript | Reactive | Bi-directional | Direction |
| :----------: | :----------------------: | :------: | :------------: | :------------------: |
| &#9989; | &#10060; | &#9989; | &#10060; | Child &rarr; Parents |
<br>
Use native DOMEvent to bubble up data from a child to any parent willing to listen to it.
<article>
<aside>
<Mermaid>{`
flowchart BT
Window
Body
Parent[Parent.svelte]
Child[Child.svelte]
Body -.- Window
Parent -.- Body
Child -->|on:myEvent| Parent
Child -->|on:myEvent| Body
Child -->|on:myEvent| Window
`}</Mermaid>
</aside>
<nav>
<details>
<summary>Parent.svelte</summary>
<HighlightSvelte
code={`<script>
import Child from "./Child.svelte"
</script>
<svelte:window on:my-custom-event={(e) => console.log('The choice is '+e.detail)} />
<Child />`}
/>
</details>
<details>
<summary>Child.svelte</summary>
<HighlightSvelte
code={`<script>
function triggerEvent(choice) {
const event = new CustomEvent('my-custom-event', {detail: choice});
window.dispatchEvent(event)
}
</script>
<button on:click={() => triggerEvent('A')}>Select A</button>
<button on:click={() => triggerEvent('B')}>Select B</button>`}
/>
</details>
</nav>
</article>
<style>
article {
border: 3px solid var(--primary-color);
display: grid;
grid-template-columns: auto 1fr;
padding: 1ex;
margin: 1ex 0;
}
article header {
font-weight: bold;
font-size: 1.2rem;
text-align: center;
padding-bottom: 1ex;
margin-bottom: 1ex;
border-bottom: 1px solid var(--primary-color);
grid-column: 1/3
}
article footer {
font-size: 0.8rem;
padding-top: 1ex;
margin-top: 1px;
border-top: 1px solid var(--primary-color);
grid-column: 1/3
}
article details :global(pre) {
font-size: 0.8em
}
</style>