Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to trigger/force update a Svelte component

Tags:

svelte

I am trying to get my head around the svelte 3 reactivity thing...

  1. I wanted to force refreshing a UI on a button click. I am using a custom component AsyncFetcher that accepts HTTP post data, and returns data object (http post result) for its slot.

  2. I wanted to have a disable functionality. So when the "Disable" button is clicked an http api is called followed by a refresh of the data view.

<script>
    export let id

    function onDisable() {
        fetch('disable-api-url', {id: id})
        // Then ??
        // What to do after the fetch call, to refresh the view
    }
</script>

<AsyncFetcher postParam={id} let:data>
    {data.name}

    <button on:click={??}>Refresh</button>
    <button on:click={onDisable}>Disable Item</button>
</AsyncFetcher>

I tried doing on:click={() => id=id} to trick it to refresh to no avail. If id would have been an object rather than string id={...id} would have worked, which unfortunately, is not the case here.

What would be a correct way to achieve this?

like image 872
Leone Avatar asked Jul 04 '19 15:07

Leone


3 Answers

Using a component to manage fetches is very unconventional. Typically you'd fetch data inside onMount, or in an event handler:

<script>
  import { onMount } from 'svelte';

  let initialData;
  let otherData;

  onMount(async () => {
    const res = await fetch('some-url');
    initialData = await res.json();
  });

  async function update() {
    const res = await fetch('some-other-url');
    otherData = await res.json();
  }
</script>

{#if initialData}
  <p>the data is {initialData.something}</p>
{/if}

<button on:click={update}>update</button>
like image 148
Rich Harris Avatar answered Nov 14 '22 15:11

Rich Harris


To fetch data use the await block:

<script>
    async function fetchData() {
        const res = await fetch('/api')
        const data = await res.json

        if (res.ok) {
            return data
        } else {
            throw new Error(data)
        }
    }
</script>

<style>
    .error {
        color: red;
    }
</style>

{#await fetchData}
    <p>Fetching...</p>
{:then data}
    <div>{JSON.stringify(data)}</div>
{:catch error}
    <div class="error">{error.message}</div>
{/await}

To refresh the data you need to trigger a rerender by updating a piece of related state, since this will rerun the await block. You can trigger a rerender by storing the fetch function in a piece of state and reassigning it when the refresh button is clicked:

<script>
    async function fetchData() {
        const res = await fetch('/api')
        const data = await res.json

        if (res.ok) {
            return data
        } else {
            throw new Error(data)
        }
    }

    let promise = fetchData()
</script>

<style>
    .error {
        color: red;
    }
</style>

<button on:click="{() => {promise = fetchdata()}}">Refresh</button>

{#await promise}
    <p>Fetching...</p>
{:then data}
    <div>{JSON.stringify(data)}</div>
{:catch error}
    <div class="error">{error.message}</div>
{/await}
like image 38
Aryan Beezadhur Avatar answered Nov 14 '22 16:11

Aryan Beezadhur


While Rich Harris gives a completely serviceable answer, here's a solution for forcing Svelte to update a component to reflect an external change of its data (also posted here).

main.js; vanilla from the examples online, no special changes:

import App from './App.svelte';

var app = new App({
    target: document.body
});

export default app;

index.html; Note window.neek = {...}:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Svelte app</title>
    <script>
        window.neek = { nick: true, camp: { bell: "Neek" }, counter: 0 };
    </script>
    <script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

App.svelte; Note $: notneek = window.neek and window.neek.update = ...:

<script>
    let name = 'world';
    $: notneek = window.neek;

    function handleClick() {
        notneek.counter += 1;
    }

    window.neek.update = function () {
        notneek = notneek;
    }
</script>

<h1>Hello { notneek.camp.bell }!</h1>

<button on:click={handleClick}>
    Clicked {notneek.counter} {notneek.counter === 1 ? 'time' : 'times'}
</button>

Since the update function is within the scope of App.svelte, it is able to force the re-render when called via window.neek.update(). This setup uses window.neek.counter for the internal data utilized by the button (via notneek.counter) and allows for the deep properties (e.g. neek.camp.bell = "ish") to be updated outside of the component and reflected once neek.update() is called.

In the console, type window.neek.camp.bell = "Bill" and note that Hello Neek! has not been updated. Now, type window.neek.update() in the console and the UI will update to Hello Bill!.

Best of all, you can be as granular as you want within the update function so that only the pieces you want to be synchronized will be.

like image 13
Campbeln Avatar answered Nov 14 '22 15:11

Campbeln