Table of Contents
Disclaimer
Svelte 5 is a work in progress. It is not ready for production use. This post is currently just a set of ideas based on previews of the new features. It will be updated as the features are finalized.
Writeable / Ref
Svelte 4 had a simple writeable
store which allowed you to create a global shared state easily. Svelte 5 does not yet have such a method, but it is super easy to create one using the new $state
hook. This ref
method is almost functionally equivalent in usage to the writeable
store from Svelte 4, except that it uses a getter/setter instead of the magic $
syntax. And that is because the original writeable
is an observable, whereas $state
is a signal. Check out this article to learn the difference. To summarize, an observable is subscribed to, whereas a signal is just a value.
class Ref<T> {
_value: T;
constructor(initial: T) {
let state = $state(initial);
this._value = state;
}
get value(): T { return this._value }
set value(newState: T) { this._value = newState }
}
function ref<T>(initial: T) {
return new Ref(initial);
}
Getters and setters on classes are more performant than on objects, but if you prefer the object syntax, you can use this version instead.
function ref<T>(initial: T) {
let state = $state(initial);
return {
get value() { return state },
set value(newState: T) { state = newState }
};
}
Runify Nested Objects
This utility function is useful for creating reactive objects from JSON data. The idea was first created by this Twitter user and modified by @PaoloRicciuti. I added TypeScript types and fixed an issue with arrays and effects. Some array issues still remain.
export function deepRef<T extends object>(obj: T): T {
if(typeof obj === 'object') {
let rune = $state(obj);
for(let key in rune) {
rune[key] = deepRef(rune[key] as T);
}
return new Proxy(rune, {
get(target, prop) {
if (typeof target[prop] === "object" && "$" in target[prop]) {
return target[prop].$;
}
if(isNaN(parseInt(prop))) return Reflect.get(...arguments);
return target[prop];
},
set(target, prop, value) {
if (Array.isArray(value)) {
target[prop] = deepRef(value as T);
return true;
}
if (typeof target[prop] === "object" && "$" in target[prop]) {
target[prop].$ = value;
return true;
}
if(isNaN(parseInt(prop))) return Reflect.set(...arguments);
target[prop] = value;
return true;
}
});
}
let rune = $state(obj);
return {
get $() {
return rune;
},
set $(val) {
rune = val;
}
}
}
Solid.js createMutable()
Approach
@intrnl
on the Svelte Discord, created an alternative based on the createMutable()
function from Solid. This version is more complex, but it resolves some of the issues with the above version.
History
This is a simple utility function that can be used to create a history object, which tracks the current value and allows you to undo and redo changes to the value. Set the maxLength
to 0
to disable the history limit.
function createHistory<T>(initial: T, maxLength = 50) {
let history = $state<T[]>([initial]);
let index = $state(0);
return {
get current() { return history[index] },
set current(newState: T) {
history.splice(index + 1, history.length - index, newState);
if (maxLength > 0) history = history.slice(Math.max(0, history.length - maxLength), history.length);
index = history.length - 1;
},
get entries() { return history },
get index() { return index },
get canUndo() { return index > 0 },
get canRedo() { return index < history.length - 1 },
undo() { index = Math.max(0, index - 1) },
redo() { index = Math.min(history.length - 1, index + 1) }
};
}
Fetch Wrapper
This is a simple utility function that can be used to create a fetcher object. It is a simple wrapper around the fetch
API that provides reactivity for Svelte 5.
function createFetcher(url) {
let result = $state(null);
let loading = $state(false);
let error = $state(null);
const controller = new AbortController();
async function runFetch() {
loading = true;
try {
const resp = await fetch(url, { signal: controller.signal });
result = await resp.json();
} catch {
error = "Add error message here!";
result = null;
}
loading = false;
}
$effect(async () => {
runFetch();
return controller.abort;
});
return {
get result() { return result },
get loading() { return loading },
get error() { return error },
abort(reason) { controller.abort(reason) },
refetch: runFetch
};
}
Websocket Wrapper
This is a simple utility function that can be used to create a websocket object. It is a simple wrapper around the WebSocket
API that provides reactivity for Svelte 5.
export default function createSocket<T>(
url: string | URL,
options?: {
name?: string;
protocols?: string | string[];
onOpen?: (event: Event) => void;
onError?: (error: Event) => void;
onMessage?: (data: T, requests: T[], event: Event) => void;
}
) {
let requests = $state<T[]>([]);
let latest = $derived(requests.at(requests.length - 1));
let websocket: WebSocket | null = null;
$effect(() => {
websocket = new WebSocket(url, options?.protocols)
websocket.onopen = (event) => {
console.log(`${options?.name || "Websocket"} connected!`);
if (options?.onOpen) options.onOpen(event);
};
websocket.onerror = (error) => {
console.log(`${options?.name || "Websocket"} error:`, error);
if (options?.onError) options.onError(error);
};
websocket.onmessage = (event) => {
const data = JSON.parse(event.data) as T;
requests = requests.concat(data);
if (options?.afterMessage) options.onMessage(data, requests, event);
};
return () => {
if (websocket) websocket.close();
}
});
return {
get requests() { return requests },
get latest() { return latest },
close() {
if (websocket) websocket.close();
},
send(data: T) {
if (!websocket) throw new Error("No websocket connection");
if (websocket.readyState !== websocket.OPEN) throw new Error("No websocket connection");
websocket.send(JSON.stringify(data));
}
};
}