DEV Community

pilcrowOnPaper
pilcrowOnPaper

Posted on

Saving users’ preferences in SvelteKit

This is a quick tutorial on saving users preferences in SvelteKit.

There are 2 ways one might approach this. First is implementing an auth system. But, that might be overkill so another way is to save it locally. Let’s go with that.

So something like this... (I’ll be using this package to simplify the code)

let name : string

const saveName = () => {
    Cookie.set("name", name)
}
Enter fullscreen mode Exit fullscreen mode
<input bind:value={name}/>
<button on:click={saveName}>save</button>
Enter fullscreen mode Exit fullscreen mode

Well, that was easy.

But a small problem arises when we want to display it.

onMount(() => {
    name = Cookie.get("name")
})
Enter fullscreen mode Exit fullscreen mode
<p>{name}</p>
Enter fullscreen mode Exit fullscreen mode

This works, but since we have to wait for document to load, we need to use onMount(). That means, there will be a split second after the page loads where name = undefined. This won’t be a big problem in this case, but if it were saving user’s light/dark theme preference, it’ll lead to a quite negative UX. This will also happen if we rely on something like Firebase auth, since it also has a dependency on window/document.

To solve this, we can read the cookie in the server before the page fully loads.

First, let’s read the cookie with hooks. This handle() function runs every time SvelteKit receives a request. We’ll use the cookie package to make cookie-parsing easier.

import * as cookie from 'cookie';

export const handle : Handle = async ({ event, resolve }) => {
    const { name } = cookie.parse(event.request.headers.get('cookie') || '') as Partial<{ name: string }>;
    if (name) {
        event.locals = { name };
    }
    return await resolve(event)
}
Enter fullscreen mode Exit fullscreen mode

Next, we have to send this to the frontend. One way of doing it, is to use the session object, which can be read in the load function. We can set the session object using getSession(). Since event first passed the handle function, it includes name in locals.

export const getSession : GetSession = async (event) => {
    const { name } = event.locals as Partial<{ name: string }>;
    if (!name) return {};
    return { name };
}
Enter fullscreen mode Exit fullscreen mode

Finally, we can get the session object in the load function like below.

export const load : Load = async ({ session }) => {
    const { name } = session as Partial<{ name: string }>
    return {
        props: {
            name
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s a simple project of mine that implements this:

URL: https://niagara.vercel.app

Github: https://github.com/pilcrowOnPaper/niagara

Top comments (6)

Collapse
 
stephenlai2021 profile image
Stephen_Lai

Dude, how do we deal with two user preferences such as background-color and background-image ? The example code work on just one user preference, and I add the second one the codes not working, I think it has to do with cookie settings, how do we execute multiple preferences with the example, could you please help ? 🙏

Collapse
 
pilcrowonpaper profile image
pilcrowOnPaper

Either save 2 cookies with different names, or save the user's preferences as an object, convert to a JSON string, and save that as a cookie value.

Collapse
 
stephenlai2021 profile image
Stephen_Lai

This is my codes

hooks/index.js

import * as cookie from "cookie";

export const handle = async ({ event, resolve }) => {
const { bgColor, imageURL } = cookie.parse(event.request.headers.get('cookie') || '')

if (bgColor) event.locals = { bgColor }
if (imageURL) event.locals = { imageURL }
return await resolve(event)
}

export const getSession = async (event) => {
const { bgColor, imageURL } = event.locals
if (!bgColor) return {}
if (!imageURL) return {}
return { bgColor, imageURL }
}


routes/index.svelte

import { bgColor, imageURL } from "$lib/store";
export const load = ({ session }) => {
const locals = session;
const bgColor_preference = locals.bgColor;
const imageURL_preference = locals.imageURL;

if (bgColor_preference) bgColor.set(bgColor_preference);
if (imageURL_preference) imageURL.set(imageURL_preference);
return {};
Enter fullscreen mode Exit fullscreen mode

};


store/index.js

export const bgColor = writable('#e5ddd5')
export const imageURL = writable('https://.......')

Collapse
 
stephenlai2021 profile image
Stephen_Lai

Dude, how do we set cookie headers such as 'Expire/Max-Age', 'Secure', 'SameSite', 'HttpOnly', etc ?

Collapse
 
pilcrowonpaper profile image
pilcrowOnPaper

Depends on where you want to set your cookie.

In endpoints, you can return something like this:

return {
  headers: {
    'set-cookie': cookie
  }
}
Enter fullscreen mode Exit fullscreen mode

If you want to set it inside hooks (handle()):

const response = await resolve(event);
response.headers.append('set-cookie', cookie));
return response;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
stephenlai2021 profile image
Stephen_Lai

Thank you so much for this post, it helps a lot 🙏