Search code examples
fetchstoreserver-side-renderingsveltekit

Get values from SvelteKit's $app/stores outside of the lifecycle of a component


My Svelte components import readable stores like this:

import { classes, locations, schedule } from 'stores.ts'

In stores.ts, I want to build the URL for fetch dynamically using page.host from $app/stores.

// Note: this is not a Svelte component; it's stores.ts
import { readable } from 'svelte/store'
import { getStores } from '$app/stores'
const { page } = getStores()
let FQDN
page.subscribe(({ host }) => {
  FQDN = host
})

const getArray = async (url) => {
  const response: Response = await fetch(url)
  if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
  return await response.json()
}

const getReadableStore = (url: string) => readable([], set => {
  getArray(`http://${FQDN}${url}`)
    .then(set)
    .catch(err => console.error('Failed API call:', err))
  return () => {}
})

export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')

The sixth line throws this error...

Error: Function called outside component initialization at get_current_component (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:652:15) at Proxy.getContext (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:685:12) at Module.getStores (/.svelte-kit/dev/runtime/app/stores.js:17:26) at eval (/src/stores.ts:6:38) at instantiateModule (/Users/nates/dev/shy-svelte/node_modules/@sveltejs/kit/node_modules/vite/dist/node/chunks/dep-e9a16784.js:68197:166)

Two questions...

  1. What is the correct way to get page values from $app/stores outside of the context of a component? Is this possible? Answer from below: No, this is not possible outside the context of a component.
  2. If I'm accessing a SvelteKit site, let's say http://localhost:3000/something or https://example.com and a Svelte component loads a readable store from stores.ts, is there a way in stores.ts to determine whether the original page request that loaded the component (which loaded from stores.ts) was http or https? Answer from below: No, this is not possible in stores.ts - only from a component.

UPDATE: Based on the feedback, I'm going to set a value in my .env called VITE_WEB_URL=http://localhost:3000 and change it for the production system. This cuts down on the number of lines of code and may be a better practice (comments welcome)...

// revised stores.ts
import { readable } from 'svelte/store'

const { VITE_WEB_URL } = import.meta.env

const getArray = async (url) => {
  const response: Response = await fetch(url)
  if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
  return await response.json()
}

const getReadableStore = (url: string) => readable([], set => {
  getArray(`${VITE_WEB_URL}${url}`)
    .then(set)
    .catch(err => console.error('Failed API call:', err))
  return () => {}
})

export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')

Solution

    1. Extract from https://kit.svelte.dev/docs#modules-$app-stores

    Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with getContext.

    1. Therefore, since the readable store is bound to the context of a svelte component, I suggest you subscribe either way ($ or .subscribe) inside the component of the SvelteKit website and then send the protocol value (http or https) as parameter when it updates so that stores.ts stores it in a variable.

    However, it looks like SvelteKit does not provide the protocol value, so parse the client side window.location.href in the page subscription and then send it.