I am a bit confused here and unluckily I couldn't get any solution on the discord channel of svelte so here I go...
I have a rather basic example of two classes, let them be App
and Comp
.
App
creates a Comp
instance and then updates this instance's value
after a button click.
The Comp instance should set this value to a different variable (inputValue
) and upon changing that variable it should fire validate(inputValue)
which is reactive. Here is a REPL: https://svelte.dev/repl/1df2eb0e67b240e9b1449e52fb26eb14?version=3.25.1
App.svelte:
<script>
import Comp from './Comp.svelte';
let value = 'now: ' + Date.now();
function clickHandler(e) {
value = 'now ' + Date.now();
}
</script>
<Comp
bind:value={value}
/>
<button type="button" on:click={clickHandler}>change value</button>
Comp.svelte:
<script>
import { onMount } from 'svelte';
export let value;
let rendered = false;
let inputValue = '';
$: validate(inputValue); // This doesn't execute. Why?
function validate(val) {
console.log('validation:', val);
}
onMount(() => {
rendered = true;
});
$: if (rendered) {
updateInputValue(value);
}
function updateInputValue(val) {
console.log('updateInputValue called!');
if (!value) {
inputValue = '';
}
else {
inputValue = value;
}
}
</script>
<input type="text" bind:value={inputValue}>
So as soon as the value is changed:
if (rendered) {...}
condition is calledupdateInputValue
is called and inputValue
is changed. HTML input element is updated to this value.validate(inputValue)
never reacts to this change - WHY?
If I omit the extra call to the updateInputValue
function in the reactive if (rendered)
condition and put updateInputValue
function body's code directly to the condition, then validate(inputValue)
is triggered correctly, i.e.:
// works like this
$: if (rendered) {
if (!value) {
inputValue = '';
}
else {
inputValue = value;
}
}
So how come it doesn't work when updated in the function?
@johannchopin's answer has revealed the issue.
You can read my blogpost for slightly in-depth explanation of how reactive declaration works, here's the tl;dr:
reactive declarations are executed in batch.
svelte batches all the changes to update them in the next update cycle, and before it updates the DOM, it will execute the reactive declarations to update the reactive variables.
reactive declarations are executed in order of their dependency.
reactive declarations are executed in batch, and each declaration is executed once. some declarations are depending on each other, eg:
let count = 0;
$: double = count * 2;
$: quadruple = double * 2;
in this case, quadruple
depends on double
. so regardless of order of your reactive declarations, $: double = count * 2;
before $: quadruple = double * 2
or the other way round, the former should be and will be executed before the latter.
Svelte will sort the declarations in the dependency order.
In cases where there's no dependency relationship with each other:
$: validate(inputValue);
$: if (rendered) updateInputValue(value);
1st statement depends on validate
and inputValue
, 2nd statement depends on rendered
, updateInputValue
and value
, the declaration is left in the order as it is.
Now, knowing this 2 behaviors of reactive declaration, let's take a look at your REPL.
As you change inputValue
, rendered
or value
, Svelte will batch the changes, and start a new update cycle.
Right before updating the DOM, Svelte will execute all the reactive declaration in 1 go.
Because there's no dependency between the validate(inputValue);
and if (rendered) updateInputValue(value);
statements, (as explained earlier), they will be executed in order.
If you change rendered
or value
only, the 1st statement (validate(inputValue)
) will not be executed, and similarly, if you change inputValue
the 2nd statement (if (rendered) updateInputValue(value)
) will not be executed.
Now, in the updateInputValue
you change the value of inputValue
, but because we are already in an update cycle, we wont start a new one.
This usually isn't a problem, because if we sort reactive declarations in dependency order, the statement that updates the variable depended on will be executed before the statement that relies on the variable.
So, knowing what's wrong, there's a few "solutions" you can go for.
See the difference of ordering the reactive declaration in this REPL
So change your REPL to:
$: if (rendered) {
updateInputValue(value);
}
$: validate(inputValue);
See REPL
$: validate(inputValue);
$: if (rendered) {
inputValue = updateInputValue(value);
}
function updateInputValue(val) {
console.log('updateInputValue called!');
if (!value) {
return '';
}
else {
return value;
}
}
See REPL
Really strange (and I couldn't really explain it) but if you put the reactive statement $: validate(inputValue);
after the function updateInputValue
declaration, it's working as expected:
<script>
import { onMount } from 'svelte';
export let value;
let rendered = false;
let inputValue = '';
function validate(val) {
console.log('validation:', val);
}
onMount(() => {
rendered = true;
});
$: if (rendered) {
updateInputValue(value);
}
function updateInputValue(val) {
console.log('updateInputValue called!');
if (!value) {
inputValue = '';
}
else {
inputValue = value;
}
}
$: validate(inputValue);
</script>
Check this REPL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With