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?