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.