Saturday | 28 SEP 2024
[ previous ]

Sveltekit and Debounce

Title:
Date: 2024-09-28

I'm currently working on an appliction where I want it to act like paper. The core idea is that any changes you make should automatically be saved.

I realized pretty quickly that saving on every keystroke and every change of an object would get ridiculous. I needed to wait until the user was done doing something before triggering a save. This is where debouncing comes in. Josh Comeau has a snippet and an explanation that I can't top so here is the link.

I'm writing my application using sveltekit and so I had to make a tiny modification to get the debounce to work. I removed the window references.

    const debounce = (callback, wait) => {
        let timeoutId = null;
        
        return (...args) => {
            clearTimeout(timeoutId);
            
            timeoutId = setTimeout(() => {
                callback.apply(null, args);
            }, wait);
        };
    }

A limitation of sveltekit is that making an object reactive to detect the changes is a bad idea. Complex values like objects will fire the reactive statement multiple times even when there are no changes. This is talked about in this github issue and there is a plan to fix this in Svelte 5.

I'm however using Svelte 4 so I needed to figure out a way to detect changes in an object without the multiple events firing.

The solution I decided on was to stringify the object with JSON.stringify and then save a copy of it named original. This way I can write a reactive statement that would check if the object really did change.

    let original = JSON.stringify(obj);
    
    $: onChange(obj);
    
    let saveDebounce = debounce(() => save(), 800);
    
    function onChange(obj) {
        if (JSON.stringify(obj) === original) {
            synced = true;
        } else {
            synced = false
            saveDebounce();
        }
    }

Here I check for changes and set a flag called synced. This flag is used to show the user if their changes have been saved or not.

I then call my saveDebounce function which is really just the debounce function from above.

The debounce makes it so that if a user is typing a paragraph, then I won't save until they stop writing for 800ms.

One key thing to note is that my save function calls fetch. Normally this would be a problem if I called fetch on the server side but because it's inside that if statement and it can only be triggered on the client side when a change is made, this is safe code. If I had not done the check, then when the page is rendered on the server, it would try to execute my save function which calls a relative path. This would then throw a sveltekit error.