Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Knowing if we are in a client side navigation or direct hit #4447

Closed
jycouet opened this issue Mar 24, 2022 · 19 comments
Closed

Knowing if we are in a client side navigation or direct hit #4447

jycouet opened this issue Mar 24, 2022 · 19 comments
Labels
feature / enhancement New feature or request p2-nice-to-have SvelteKit cannot be used by a small number of people, quality of life improvements, etc.
Milestone

Comments

@jycouet
Copy link
Contributor

jycouet commented Mar 24, 2022

Describe the problem

Let's try to describe the Feature Request with a small graph:

flowchart TB
    b3-->a2
    subgraph Page Home
    a0[New visitor]-->a1[load server]-->a2[load browser]-->a3[Enjoy the home page]
    style a0 fill:#29B56A,color:#000
    style a3 fill:#254CA0,color:#000
    end
    subgraph Page About
    b0[New visitor]-->b1[load server]-->b2[load browser]-->b3[Enjoy the about page]
    style b0 fill:#29B56A,color:#000
    style b3 fill:#254CA0,color:#000
    end
Loading

As you can see, I have 2 routes: Home and About.
On a direct hit, the load function is executed 2 times once !browser once browser
On navigation from About to Home, the load function of Home is executed 1-time browser

The problem: how can I know in a load function if I'm in a client side navigation or direct hit?

Describe the proposed solution

The best to my mind would be to have something similar to browser like:

import { clientNavigation } from '$app/navigation';

export async function load() {
    console.log(`clientNavigation`, clientNavigation);
    return {};
}

When it's a direct hit, the 2 load functions would have clientNavigation to false.
When it's a client side navigation, the load function would have clientNavigation to true.

Alternatives considered

Workaround I use today:

I have a file clientNavigation.ts

export let clientNavigation = false;

export const setClientNavigation = () => {
	clientNavigation = true;
};

And in __layout.svelte I have

<script lang="ts">
	// Todo: https://github.com/sveltejs/kit/issues/4048#issuecomment-1073577686
	import { afterNavigate } from '$app/navigation';
	import { setClientNavigation } from '$lib/clientNavigation';

	afterNavigate(() => setClientNavigation());
</script>

And now I can use clientNavigation value to know if I'm in a client side navigation or direct hit.

Importance

would make my life easier

Additional Information

This would allow nice scenarios for my library https://github.com/jycouet/kitql awaiting or not to fill stores depending on the situation.

I would be happy to contribute (very happy actually!), just let me know:

  • If I forgot some part of the use case, things to consider, ...
  • Where to start?
@mrkishi mrkishi added feature / enhancement New feature or request p2-nice-to-have SvelteKit cannot be used by a small number of people, quality of life improvements, etc. labels Mar 24, 2022
@mrkishi
Copy link
Member

mrkishi commented Mar 24, 2022

I wonder how common a use-case this is? Since workarounds are possible and they aren't even that hairy, we should consider if it warrants a new feature.

As for the API, since load() lives in context module and doesn't have access to kit stores, it might be simpler to add a new property on LoadInput. Maybe even borrowing navigating's from (and possible future) semantics?

I'd love to hear others' thoughts.

@jycouet
Copy link
Contributor Author

jycouet commented Mar 24, 2022

The workaround is not very complicated and works well yes. But it's one step that all my library users need to do to enjoy it fully. (BTW, I wrapped the 3 lines in a component to put in __layout so that it's even easier!)

For sure it's not a blocker 🙃, but I wanted to check before working on it if you would accept something in this direction or not.

Adding it to the LoadInput would not be so great on my side as developers using my lib would need to pass the property manually like 👇

export async function load({ clientNavigation }) {
    KitQL.query({ clientNavigation })
    return {};
}

Today, with the workaround, it's magic like 👇

export async function load() {
    KitQL.query()
    return {};
}

@mrkishi
Copy link
Member

mrkishi commented Mar 24, 2022

I'm actually not sure I follow your use-case after re-reading the proposal. If you only care about knowing whether or not it's the first time load has been called so you can fill some stores, couldn't you keep track of that from within your library?

Some well placed if (browser && !initialized) and initialization would be equivalent to the afterNavigate guard, no?

@jycouet
Copy link
Contributor Author

jycouet commented Mar 25, 2022

I started to think... But why I didn't think about it earlier!!!
But in the end, I don't know where to place "well" the initialized. As it's not really "initialized", it's really: "I'm I in client side? or direct hit?"

Because, I can do:

export async function load({ fetch }) {
    await KitQL.query1({ fetch })
    await KitQL.query2({ fetch })
    return {};
}

And in both query1 & query2 I would need to know in what mode I'm in.
So I can't initialize after query1.

And I can't have

import { afterNavigate } from '$app/navigation';
afterNavigate(() => console.log('Hello you'));

Because it's telling me: Function called outside component initialization

@mrkishi
Copy link
Member

mrkishi commented Mar 25, 2022

I believe the explicit addition to load would be an easier buy for people than a globally available flag—that'd be equivalent to exposing the SvelteKit's client singleton, and I'm not sure there's interest in that.

Anyway, you can still have an equivalent guard for your scenario without user input with something like this:

import { browser } from '$app/env';

export let clientStarted = false;

if (browser) {
	addEventListener('sveltekit:start', () => {
		clientStarted = true;
	});
}

clientStarted would only be true after the first page was loaded and rendered.

@jycouet
Copy link
Contributor Author

jycouet commented Mar 25, 2022

WoW, this works like a charm!
Thx a lot @mrkishi

It will improve usability of my lib 👍

@jycouet jycouet closed this as completed Mar 25, 2022
@jycouet
Copy link
Contributor Author

jycouet commented Mar 26, 2022

I found a case where the trick is not working.
=> When my lib is not used in the direct hit. The event sveltekit:start will never fire.

To avoid this, I have to put myLib.init() in the root __layout to make sure my lib is here from the start.

But coming back to the origin, it's where import { clientNavigation } from '$app/navigation'; would make sense. To have the platform providing this information.
Thoughts?

@jycouet jycouet reopened this Mar 26, 2022
@CaptainCodeman
Copy link
Contributor

I use this approach to handle switching from server-loaded Firestore data to client-side subscriptions, without re-fetching data twice when subsequently navigating between routes on the client (same deal - you want to know when the initial server -> client mode changeover has happened).

It would be convenient to have a flag, similar to dev and browser (hydrated?) especially as there isn't always a guaranteed root layout (if you have branches with resets, or in future "named" layouts).

Alternatively, some client side root hook / function would be somewhere it could go.

@jycouet
Copy link
Contributor Author

jycouet commented Apr 6, 2022

Great new video: Rich Harris - The Road to SvelteKit 1.0 (Svelte Society NYC)
Talking about navigation types: https://youtu.be/s6a1pbTVcUs?t=1501

I didn't find another issue to link this one too.
I guess that use cases are really close.

@dummdidumm
Copy link
Member

Sounds like #5276 could help solve this. If there was a hydrated store which you can listen to you also wouldn't run into the "never hit" case. But I'm also not 100% sure if this would help you as a library author because of #3010

@jycouet
Copy link
Contributor Author

jycouet commented Jul 20, 2022

In our case, a hydrated store would work because we are already generating a "library runtime" to be able to access these stores from our lib.

Doing the "library runtime" si quite some work, but allows us to support other environments! (Today: Svelte, Sapper, SvelteKit)

@dummdidumm
Copy link
Member

Both the navigation store and the before/afterNavigate hooks have a type property as of #6537 , so you can detect from that what kind of navigation you're in. Does this solve the issue?

@jycouet
Copy link
Contributor Author

jycouet commented Sep 3, 2022

Not really as these stores & hooks are not available in load function.

This (👇) is throwing error: Function called outside component initialization

import { navigating } from '$app/stores';
import { get } from 'svelte/store';
import type { PageLoad } from './$types';

export const load: PageLoad = async (event) => {
	get(navigating); // <-- error: Function called outside component initialization

	return {};
};

hydrated store would still be a great addition for me

@CaptainCodeman
Copy link
Contributor

CaptainCodeman commented Sep 3, 2022

I was using the event to be able to determine if the load function was the first client-side execution after a server-side render, or a change of page ... so the return could be a store based on fetched data or one based on an active firestore subscription.

Is it possible to do this anymore (inside the load function)? It doesn't seem so.

jycouet added a commit to jycouet/kit that referenced this issue Sep 3, 2022
@jycouet jycouet mentioned this issue Sep 3, 2022
5 tasks
@PatrickG
Copy link
Member

PatrickG commented Sep 4, 2022

Not really as these stores & hooks are not available in load function.

This (point_down) is throwing error: Function called outside component initialization

import { navigating } from '$app/stores';
import { get } from 'svelte/store';
import type { PageLoad } from './$types';

export const load: PageLoad = async (event) => {
	get(navigating); // <-- error: Function called outside component initialization

	return {};
};

hydrated store would still be a great addition for me

Shouldn't this be possible with #6100?

import { browser } from '$app/environment';
import { navigating } from '$app/stores';
import { get } from 'svelte/store';
import type { PageLoad } from './$types';

export const load: PageLoad = async (event) => {
	if (browser) {
		get(navigating); // <-- should work
	}

	return {};
};

@Rich-Harris
Copy link
Member

Per #6555 (comment), this is easily solved:

// src/lib/hydrated.js
export let hydrated = false

export function update() {
  hydrated = true;
}
<!-- src/routes/+layout.svelte -->
<script>
  import { onMount } from 'svelte';
  import { update } from '$lib/hydrated.js';

  onMount(update);
</script>

@Rich-Harris
Copy link
Member

(The reason this wasn't possible when discussing this problem earlier is that until recently there wasn't a guaranteed root layout)

@CaptainCodeman
Copy link
Contributor

Yeah, I was trying to remember "why on earth didn't I just set a flag to true?!"

@jycouet
Copy link
Contributor Author

jycouet commented Sep 4, 2022

Per #6555 (comment), I close this issue as we have:

  • a userland easy solution
  • a nice way to include this code via a vite plugin for libs

@jycouet jycouet closed this as completed Sep 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request p2-nice-to-have SvelteKit cannot be used by a small number of people, quality of life improvements, etc.
Projects
None yet
Development

No branches or pull requests

6 participants