56
loading...
This website collects cookies to deliver better user experience
// stores/user.ts
import { writable } from "svelte/store";
export const user = writable<User | null>(null);
// stores/products.ts
import { writable, get as $ } from "svelte/store";
import { user } from "../user";
export const products = writable<Product[] | null>(null);
// Remove products on log out
user.subscribe($user => {
if (!$user) {
products.set(null);
}
});
export async function loadProducts() {
// Simple caching mechanism
if (!$(products) && $(user)) {
const response = await fetch('/api/products');
products.set(await response.json());
}
}
Mutating any shared state on the server will affect all clients, not just the current one.
input.session
in a load(input)
function, or through getStores().session
within a component.const stores = new WeakMap<Session, ...>();
stores.get(input.session)
when in a load
function or stores.get($session)
when in a component.getStores()
. Inside a load
function, there's no built-in way, however.useLoad
function, that is called during load
:export function load(input) {
useLoad(input, func1, func2, ...);
}
func1
, func2
and so on with the session in input.session
as the context.useSession()
that can get the current session from anywhere.// useLoad.ts
import type { LoadInput } from "@sveltejs/kit";
type SessionData = { stores: Map<unknown, unknown>; fetch: typeof fetch };
export const loadSession = writable<Session | null>(null);
export const sessionData = new WeakMap<Session, SessionData>();
// Typescript magic
type ReturnTypes<T> = T extends [...infer U, infer A]
? A extends (...args: unknown[]) => unknown
? ReturnType<A> & ReturnTypes<U>
: void
: void;
export function useLoad<T extends Array<(...args: unknown[]) => unknown>>(input: LoadInput, ...fns: T): ReturnTypes<T> {
if (!sessionData.has(input.session)) {
sessionData.set(input.session, { stores: new Map(), fetch: input.fetch });
}
loadSession.set(input.session);
try {
return Object.assign({}, ...fns.map((fn) => fn()));
} finally {
loadSession.set(null);
}
}
// useSession.ts
import { getStores } from "$app/stores";
import { get as $, writable } from "svelte/store";
import { loadSession, sessionData } from "../useLoad";
export function useSession(): { session: Session; data: SessionData } {
const session = $(loadSession) ?? ($(getStores().session) as Session);
const data = sessionData.get(session);
if (!data) {
throw new Error("Call useLoad before calls to useSession");
}
return {
session,
data,
};
}
useLoad(input, ...)
that needs to be called inside the load function.useSession()
which can get the current session, even within the load function.defineStore
function.// useUser.ts
import { writable } from "svelte/store";
import { defineStore } from "./defineStore";
const useUser = defineStore(() => {
const user = writable<User|null>(null);
return {
user
}
});
// useProducts.ts
import { writable } from "svelte/store";
import { defineStore } from "./defineStore";
import { useUser } from "./useUser";
const useProducts = defineStore(() => {
const { user } = useUser();
const products = writable<Products|null>(null);
user.subscribe($user => {
if (!$user) {
products.set(null);
}
}
return {
products
};
});
user
store!defineStore
takes a function as a parameter. The first time the store is needed for the current session, the function is executed. The subsequent times, the cached result is returned:import { useSession } from "./useSession";
export function defineStore<T>(fn: () => T): () => T {
return () => {
const { data } = useSession();
if (!data.stores!.has(fn)) {
data.stores!.set(fn, fn());
}
return data.stores!.get(fn) as T;
};
}
fetch
function that should be used within load
. The reason is so that when the same calls are executed both serverside & clientside, the cached value can be used clientside instead of repeating the call.useLoad
, we stored input.fetch
in sessionData
.useFetch
function:export const useFetch = () => {
const { data } = useSession();
return { fetch: data.fetch };
}
useProducts
function to add loadProducts
:// useProducts.ts
import { browser } from "$app/env";
import { writable, get as $ } from "svelte/store";
import { defineStore } from "./defineStore";
import { useUser } from "./useUser";
const useProducts = defineStore(() => {
const { user } = useUser();
const { fetch } = useFetch();
const products = writable<Products|null>(null);
user.subscribe($user => {
if (!$user) {
products.set(null);
}
}
const loadProducts = async () => {
if (!$(products) && $(user)) {
const response = await fetch('/api/products');
products.set(await response.json());
}
}
return {
products,
loadProducts
};
});
useXxx
naming. Are we doing React? 😅useLoad
load
function. Here is what it would look like:import { get as storeGet } from "svelte/store";
import { useLoad } from "$lib/use/useLoad";
import { useProduct } from "$lib/use/useProduct";
export async function load(input: LoadInput) {
const {products, loadProducts} = useLoad(input, useProducts);
await loadProducts();
return {
products: storeGet(products);
}
}
<script lang="ts">
import { useProduct } from "$lib/use/useProduct";
const {products, loadProducts} = useProducts();
const {user} = useUser();
$: loadProducts(), [$user] // reloads products each time user changes
</script>
{#each ($products || []) as product}
<Product {product}/>
{/each}
load
function or inside a component.fetch
function ;)