See https://svelte.dev/repl/89b164da451a45a48ad12ca8f113b13a?version=4.2.15. This a condensed example of what’s going on in my application.
- There are 3 components:
App
, which rendersLayout
, which rendersNavigation
. - There are two stores:
url
andusername
. App
has a reactive statement$: if (!$username && $url !== '/login') { url.set('/login') }
(e.g. to redirect back to the login page if not logged in).Navigation
is conditionally rendered only if the URL is not/login
.Navigation
throws an error ifusername
is falsey.- Pressing the logout button does
username.set(null)
.
stores.js:
import { writable } from 'svelte/store';
export const username = writable();
export const url = writable('/login');
App.svelte:
<script>
import { url, username } from './stores';
import Layout from './Layout.svelte';
$: {
console.log('App update', $url, $username);
if (!$username && $url !== '/login') {
console.info('Redirect to login');
url.set('/login');
}
}
</script>
{console.log('App render', $url, $username), ''}
<Layout />
Layout.svelte:
<script>
import { url, username } from './stores';
import Navigation from './Navigation.svelte';
// Adding $username here seems to fix it?
$: console.warn('Layout update', $url);
</script>
{(console.warn('Layout render', $url), '')}
{#if $url !== '/login'}
<Navigation />
<button on:click={() => {
username.set(null);
}}>LOGOUT</button>
{:else}
<button on:click={() => {
username.set('admin');
url.set('/');
}}>LOGIN</button>
{/if}
Navigation.svelte:
<script>
import { url, username } from './stores';
$: try {
console.log('Nav update', $url, $username);
if (!$username) {
throw new Error('Invalid user');
}
} catch (err) {
console.error('EEE', err);
//throw err;
}
</script>
{console.log('Nav render', $url), ''}
What should happen:
- Pressing the logout button sets
username
to null. App
reacts tousername
changing and setsurl
to/login
.Layout
reacts tourl
changing and unmountsNavigation
.Navigation
doesn’t throw an error.
What actually happens:
- Pressing the logout button sets
username
to null. App
reacts tousername
changing and setsurl
to/login
.Navigation
reacts tousername
changing and throws an error.- (if we catch the error and allow svelte to continue)
Layout
reacts tourl
changing and unmountsNavigation
.
What I would like to understand is why this is happening. Why does svelte update the parent and the child before updating the middle component?
- Passing the url down to
Layout
as prop doesn’t seem to fix it which I find even more surprising. - Adding
$username
toLayout
fixes it.
How can you safely use conditional rendering if it’s not guaranteed that a component will be unmounted before it gets updated with potentially invalid state? What am I missing here?
2