Reputation: 3413
I am trying to get my head around the svelte 3 reactivity thing...
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.
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?
Upvotes: 29
Views: 62478
Reputation: 2063
sub.svelte.ts
LastUpdate: number = $state(0);
OnPropertyChanged() {
this.LastUpdate = Date.now()
}
page.svelte
function(){
...
sub.OnPropertyChanged() ;
...
}
{#key sub.LastUpdate}
<Content ...
{/key}
Upvotes: 1
Reputation: 280
The key (no pun intended) to make a component fully reload, and not just update the inside values, is to use {#key value_to_watch}
like:
{#key category_on}
<Testone a={category_on} />
{/key}
If category_on
changes the <Testone/>
component is fully reloaded
REPL
Upvotes: 6
Reputation: 4806
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}
Upvotes: 7
Reputation: 57
I did this (made the component disappear and reappear with a timer):
<script>
import ForcedToRerender from './ForcedToRerender.svelte'
let visible = true
let rerender = () =>
{
visible=false
setTimeout(()=>{visible = true}, 100)
}
</script>
{#if visible}
<ForcedToRerender />
{/if}
<button on:click={rerender}>Rerender</button>
ForcedToRerender.svelte:
<script>
import { onMount } from 'svelte'
let num = 0
let rnd = () => num = Math.random()
onMount(rnd)
</script>
<div on:click={rnd}>
{num}
</div>
This works, as you can see here.
Upvotes: 2
Reputation: 41
Here's a somewhat hacky solution for forcing a rerender of a component not dependent on external data:
<script>
// Await immediately resolved promise to react to value change.
const forceUpdate = async (_) => {};
let doRerender = 0;
</script>
{#await forceUpdate(doRerender) then _}
<ForcedToRerender on:click={() => doRerender++} />
{/await}
I tried to find a more "native" solution, but this is what I ended up with. REPL: https://svelte.dev/repl/2dc5c7ca82bc450f8f7dd25d2be577b1?version=3.43.0
Upvotes: 4
Reputation: 3609
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()
)
Upvotes: 1
Reputation: 2990
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.
Upvotes: 14
Reputation: 29615
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>
Upvotes: 23