How to trigger/force update a Svelte component
in my case, svelte did not flush the output,
cos i blocked the javascript event loop by running a benchmark at 100% cpu
in this case, the trick is to manually unblock the event loop with await sleep(10)
<script>
function sleep(millisec = 0) {
return new Promise((resolve, reject) => {
setTimeout(_ => resolve(), millisec);
});
};
let result = '';
async function runBenchmark() {
for (let step = 0; step < 10; step++) {
// this needs 100% cpu, so no time for svelte render
cpuburn(); result += `${step}: 1.234 sec\n`;
// unblock the JS event loop, so svelte can render
await sleep(10);
}
}
</script>
<pre>{result}</pre>
here is a repl (but currently it triggers a bug in the repl runtime)
solving this with synchronous function calls is probably not possible
(something like a $$svelte.forceTickSync()
)
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.
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>
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}