I want to debounce a signal for some effects, but not others

In short, I'm using a slider to allow the user to quickly adjust font scale linearly. I need the font to respond to the 1000s of adjustments, but I do not want to update the history buffer with every minor font change that the slider will trigger. I tried to get the primitives to work, but failed to find a way to have both instant and bounced effects without having to write a lot of code. https://primitives.solidjs.community/package/scheduled#debounce My solution is pretty basic, but works well. I'm curious if there's a better way.
import {createSignal} from "solid-js"
export function debouncedSignal(value, delay = 500) {
const [signal, setSignal] = createSignal(value)
const [debounced, setDebounced] = createSignal(value)
let time = Date.now()

function set(value) {
const now = Date.now()
if (now - time > delay) {
time = now
setDebounced(() => value)
}
setSignal(() => value)
}
return [signal, set, debounced]
}
import {createSignal} from "solid-js"
export function debouncedSignal(value, delay = 500) {
const [signal, setSignal] = createSignal(value)
const [debounced, setDebounced] = createSignal(value)
let time = Date.now()

function set(value) {
const now = Date.now()
if (now - time > delay) {
time = now
setDebounced(() => value)
}
setSignal(() => value)
}
return [signal, set, debounced]
}
In this way, you can use the signal normally or the debounced value. For example,
const [size, setSize, debouncedSize] = debouncedSignal(0)

createEffect(()=>{
size() // will trigger instantly
})

createEffect(()=>{
debouncedSize() // will trigger every 500 ms
})
const [size, setSize, debouncedSize] = debouncedSignal(0)

createEffect(()=>{
size() // will trigger instantly
})

createEffect(()=>{
debouncedSize() // will trigger every 500 ms
})
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
10 Replies
zulu
zuluā€¢5d ago
if it works it works if you want to use the solid primitives maybe the "leading" can help to get the instant , and bounced https://primitives.solidjs.community/package/scheduled/#leadingandtrailing https://primitives.solidjs.community/package/memo/#createthrottledmemo
peerreynders
peerreyndersā€¢4d ago
Another way to approach it:
function debouncedSignal<T>(value: T, delay = 500) {
const [signal, set] = createSignal(value);
const derived = createMemo<[T, number]>(
(prev) => {
const next = signal();
const now = Date.now();
return now - prev[1] < delay ? prev : [next, now];
},
[value, 0]
);
const debounced = () => derived()[0];

return [signal, set, debounced] as const;
}
function debouncedSignal<T>(value: T, delay = 500) {
const [signal, set] = createSignal(value);
const derived = createMemo<[T, number]>(
(prev) => {
const next = signal();
const now = Date.now();
return now - prev[1] < delay ? prev : [next, now];
},
[value, 0]
);
const debounced = () => derived()[0];

return [signal, set, debounced] as const;
}
https://playground.solidjs.com/anonymous/6a90b3ba-ceb0-431d-9143-42395dc3c7f0
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zulu
zuluā€¢4d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zulu
zuluā€¢4d ago
I might be confused but isn't this more like a throttle ? I mean you probably just refactored the OP code to use the memo as the improvement following the same logic he used
peerreynders
peerreyndersā€¢4d ago
I'm not sure it's an improvement functionally but it's more in the interest of moving from a (signals + effect = reactivity) perspective to embracing reactive derivations. It's definitely throttle rather than debounce. And the issue that I have with this implementation is that it can stay at a stale value with reference to a stabilized source value past the configured delay. I think what is intuitively expected is limiting the rate of change but settling on the source value if it is stable past the delay window. As you already pointed out I think:
import {
createScheduled,
leadingAndTrailing,
throttle,
} from '@solid-primitives/scheduled';

function throttledSignal<T>(value: T, delay = 500) {
const [signal, set] = createSignal(value);
const scheduled = createScheduled((fn) =>
leadingAndTrailing(throttle, fn, delay)
);
const throttled = createMemo<T>((prev) => {
const next = signal();
return scheduled() ? next : prev;
}, value);

// for debugging only
createEffect(() => {
const result = scheduled();
const value = signal();
if (result) console.log(result, value, Date.now());
});

return [signal, set, throttled] as const;
}
import {
createScheduled,
leadingAndTrailing,
throttle,
} from '@solid-primitives/scheduled';

function throttledSignal<T>(value: T, delay = 500) {
const [signal, set] = createSignal(value);
const scheduled = createScheduled((fn) =>
leadingAndTrailing(throttle, fn, delay)
);
const throttled = createMemo<T>((prev) => {
const next = signal();
return scheduled() ? next : prev;
}, value);

// for debugging only
createEffect(() => {
const result = scheduled();
const value = signal();
if (result) console.log(result, value, Date.now());
});

return [signal, set, throttled] as const;
}
is a better fit. https://playground.solidjs.com/anonymous/09d9a355-73f5-495d-bb7c-f17842e7f3c3
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
RxMarbles: Interactive diagrams of Rx Observables
Learn, build, and test Rx functions on Observables
zulu
zuluā€¢4d ago
you are spot on, that was one of my concerns. when it popped out to me that it is not the type of debounce I would expect I also later found out that solid primitives provide createThrottledMemo I linked in my top message I am also realizing that there are different implementations to debounce with some actually being a hybrid of a throttle which I think is what lodash implementation . essentially, debounce with a deadline in most web cases I am assuming that developers will want the last trigger to apply after the denounced period this at least apply for usage such as "throttling" key storkes to search, but there is probably differnt use cases and different needs. and there is debounce implementations, that may not update in some cases such as when the interval of trigger is smaller than the debounce continuously I think that is how the solid primitive will work with ( leading+trailing+debounce)
think what is intuitively expected is limiting the rate of change but settling on the source value if it is stable past the delay window.
šŸ‘ for future reference I found that the primitives of solid shows their implementation behavior in a interactive demo https://primitives.solidjs.community/playground/scheduled/
peerreynders
peerreyndersā€¢4d ago
I also later found out that solid primitives provide createThrottledMemo
Which has been deprecated but it was because of the supplied alternative I started digging into createScheduled. Though describing the return value as a signal is a bit of a stretch.
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
GitHub
solid-primitives/packages/scheduled/src/index.ts at fb78a634c3e9c36...
A library of high-quality primitives that extend SolidJS reactivity. - solidjs-community/solid-primitives
peerreynders
peerreyndersā€¢4d ago
I'm not sure how often leading is useful as it can get stuck on a stale value. (Which is of course exactly what the original problem here was)
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
zulu
zuluā€¢4d ago
I think there are just different use cases possible, the important thing is to just understand what each configuration do
zulu
zuluā€¢3d ago
leading+trailing throttle is interesting sometimes it seem the leading and tailing occur one after the other, I guess it is a timing thing
No description

Did you find this page helpful?