Open KieranP opened 4 days ago
Hello,
You could just use a boolean to ignore the first call.
Something like this:
let params = $state($applicationContextStore.params)
let first_run = true;
$effect(() => {
if (first_run) {
params; // just to trigger on next change
first_run = false;
return;
}
fetchData({
url: '/users',
params,
onSuccess: (newUsers: User[]) => {
users = newUsers
})
})
});
Another option.
Not sure about your exact use case, but it seems that it's better to avoid using effects unless there is no other way. You can just create a click handler that takes sort
as a parameter, e.g. Click Handler Example
Also, I would use a <button>
instead of the <a>
anchor tags as this is a user action vs a navigation. it's better for accessibility. You can just create an unstyled button with no borders, backgrounds, etc. to make it look like a link.
@adiguba I had considered that, but it just doesn't feel very Svelte-like. It would be really great if Svelte had a $watch/$track/$monitor rune for this purpose, where it doesn't run when component is mounted, but does when any $state is referenced within is updated. e.g,.
<script lang="ts">
interface Props {
users: User[]
}
let { users }: Props = $props()
let params = $state($applicationContextStore.params)
$watch(() => {
fetchData({
url: '/users',
params,
onSuccess: (newUsers: User[]) => {
users = newUsers
})
})
})
</script>
@Leonidaz The example is extremely trimmed down and simplified to explain the core issue. In our actual app, we have a table of data, with each column being sortable, both asc & desc, we have a pagination component, and we have a filters sidebar with various search and filtering options. Any time any of them change, it updates a centralized params store, which in turn triggers the fetchData call through reactivity. It would be cumbersome to replace that with many click handlers.
It would be really great if Svelte had a $watch/$track/$monitor rune for this purpose
With signal, you need to execute the code to detect the reactive value, so they must be declared in some way. But you can write something similar like this :
export function watch(get_states: () => any[], fn: () => (void | (() => void))) {
let first_run = true;
$effect(() => {
get_states();
if (first_run) {
first_run = false;
} else {
return fn();
}
});
}
Usage :
watch(
// States to watch on startup :
() => params,
// Effect :
() => console.log("watch() : fetchData(" + JSON.stringify(params) + ")")
);
Otherwise, I think that @Leonidaz suggestion is way better if you got the full control on the param
state.
And it may even be simplier, as you can replace all your event like () => {params = { ...params, sort: 'asc' }}
with a simple function call : () => updateParams({sort: 'asc' })
@adiguba both are awesome suggestions! 👍 The watch wrapper is really nice!
@KieranP I think either of these or combined together (one function to update) should work. The way signals work with effect, I don't think there is a better way other than setting a first run boolean. A wrapper for watch seems to do the trick to avoid declaring these first run vars everywhere.
I modified the @adiguba's watch example to just update the parameters that changed instead of destructuring a whole new params each time.
This way you can avoid changes unless something actually changed. So, in this example, if the same sort is applied, the fetch won't rerun. Playground: run less changes
But at the same time you can watch the whole params
object by destructuring it in the watch. Or individual params properties can be watched.
@KieranP your Svelte 4 example seems to be oversimplified and missing something important - the $:
blocks also run during the component initialization. Also, unlike $:
, $effect
doesn't run on the server.
I really don't understand the Svelte 4 example, as that too runs on component init just like Svelte 5. As per the suggestions above, there are plenty of ways to get the desired behaviour you want without us needing to expand the API space to add something that probably isn't needed anyway.
I think the better approach here, and one we've been recommending in the docs is to do this work in the event handler where possible, to avoid effects entirely. Effects are designed to be used for side-effects outside of your app, but in this case, you're doing something based off an action rather than a side-effect.
Describe the bug
On page load, we pass the initial data into the component. Because of this, we don't need to fetch data again when the component is mounted, but if the query params change, we need to trigger a section of code to refetch results.
In Svelte 4, we had something like this (trimmed and simplified but it should be clear): (
$applicationContextStore.params
is just a store that initialized with params from the current URL)This worked well. Users were passed in when the component was rendered for the first time. Then if the sorting links were clicked, the params were updated, triggering the fetchData block.
With Svelte 5, there does not appear to be any way to monitor a block of code for state changes without it also first running.
The above doesn't work as intended. $derived, $derived.by, $effect, $effect.pre, and onMount all run the code when the component is mounted, which is not what we want, because we are already passing in the initial data. This ends up doubling requests which returns exactly the same data.
What we need is a $track function or something, that doesn't run when component is mounted, but does run when any state vairables in it are run.
On a different note, the migrator is converting it to use legacy/run, which behind the scenes looks like $effect.pre, so the migrator is recommending a change that breaks functionality.
Any ideas/advice?
Reproduction
See above
Logs
No response
System Info
Severity
blocking an upgrade