S
SolidJS22h ago
snorbi

Solving "computations created outside ..." using `createMemo() ?`

I got the following warning because I used props.values in my event handling code:
computations created outside a createRoot or render will never be disposed
I have read the docs and some additional info (like https://github.com/solidjs/solid/issues/2130). The warning is gone when I simply wrap the props.values access in a createMemo():
const values = createMemo(() => props.values)
const values = createMemo(() => props.values)
My question is: is it a good solution? If yes, then why the props accessors are not functions themselves (like props.values()instead of the current props.values) so they would always return a "fresh" value? Thanks.
6 Replies
peerreynders
peerreynders21h ago
because I used props.values in my event handling code
Event handlers aren't tracked; so something in the code is preventing the linter from recognizing it as event handler code.
My question is: is it a good solution?
The typical countermeasure is untrack
why the props accessors are not functions themselves
They are getters, so in a sense they are functions … In this instance the error is more about the fact that reactive values are being accessed in a non-reactive context, rendering them in fact non-reactive. untrack clearly communicates the intent that this is a non-reactive (non-subscribing) access. That said, untrack shouldn't be necessary in an event handler.
peerreynders
peerreynders20h ago
Building a Reactive Library from Scratch can help to build a workable mental model.
DEV Community
Building a Reactive Library from Scratch
In the previous article A Hands-on Introduction to Fine-Grained Reactivity I explain the concepts...
Andreas Roth
Andreas Roth20h ago
The initial questions seems fishy or incomplete... The warning is that computations cannot be CREATED outside a reactive context. Accessing memos or signal fields should never CREATE a new computation, just read the current value, and this is totally fine to do outside of a reactive context. So you should trace the path from props.values through the parent components to the source to see what is happening... My guess is that you have defined a getter somewhere, that creates a memo or something like that
snorbi
snorbiOP19h ago
untrack() does not solve the issue, the warning still arises... So I must do something incorrectly, as @Andreas Roth suggested 😦 The code is something like this:
export type Binding<T> = {
get: () => T
set(value: T): void
}

export function createBinding<T>(getter: () => T, setter: (value: T) => void): Binding<T | null> {
return { get: getter, set: setter }
}

...

export type ComboboxProps<T extends NonNullable<any>> = JSX.SelectHTMLAttributes<HTMLSelectElement> & {
binding: Binding<T | null>
values: T[]
}

...

export function Combobox<T extends NonNullable<any>>(props: ComboboxProps<T>): JSX.Element {
...

let ref: HTMLSelectElement | undefined

const values = () => props.values // <-- using `createMemo(() => props.values)` solves the warning

const handleChange = () => {
const value = ref!.value
props.binding.set(value != null && value != "" ? values().filter(e => keyProvider(e) == value)[0] : null) // <-- The warning is issued at this line; it is interesting that `props.binding` does not cause a warning
}

onMount(() => ref!.addEventListener("input", handleChange))
onCleanup(() => ref!.removeEventListener("input", handleChange))

return (
<select ref={ref} ...>
...
</select>
);
}
export type Binding<T> = {
get: () => T
set(value: T): void
}

export function createBinding<T>(getter: () => T, setter: (value: T) => void): Binding<T | null> {
return { get: getter, set: setter }
}

...

export type ComboboxProps<T extends NonNullable<any>> = JSX.SelectHTMLAttributes<HTMLSelectElement> & {
binding: Binding<T | null>
values: T[]
}

...

export function Combobox<T extends NonNullable<any>>(props: ComboboxProps<T>): JSX.Element {
...

let ref: HTMLSelectElement | undefined

const values = () => props.values // <-- using `createMemo(() => props.values)` solves the warning

const handleChange = () => {
const value = ref!.value
props.binding.set(value != null && value != "" ? values().filter(e => keyProvider(e) == value)[0] : null) // <-- The warning is issued at this line; it is interesting that `props.binding` does not cause a warning
}

onMount(() => ref!.addEventListener("input", handleChange))
onCleanup(() => ref!.removeEventListener("input", handleChange))

return (
<select ref={ref} ...>
...
</select>
);
}
And the usage is something like:
function CheckoutPage() {
...
const [selectedPaymentOption, setSelectedPaymentOption] = createSignal<PaymentOption | null>(null)
...
return (
...
<Combobox binding={createBinding(selectedPaymentOption, setSelectedPaymentOption)} values={<<a bit more complex filtering/mapping based on a Signal>>} ... />
...
)
function CheckoutPage() {
...
const [selectedPaymentOption, setSelectedPaymentOption] = createSignal<PaymentOption | null>(null)
...
return (
...
<Combobox binding={createBinding(selectedPaymentOption, setSelectedPaymentOption)} values={<<a bit more complex filtering/mapping based on a Signal>>} ... />
...
)
Thanks. Ps.: I'm relatively new in both TypeScript/Javascript and SolidJS
bigmistqke
bigmistqke17h ago
(tip: you can get syntax highlighting with ``tsx) > If yes, then why the props accessors are not functions themselves (like props.values()instead of the current props.values) so they would always return a "fresh" value? the error you are getting is not about props.values` being incorrect values, but it's a warning about a possible memory leak.
peerreynders
peerreynders16h ago
A simple attempt at a reproduction doesn't yield a similar warning https://playground.solidjs.com/anonymous/5d394788-c3d4-40c9-adf4-90e1d3b9fb95 Which makes me wonder whether whatever is stored in <<a bit more complex filtering/mapping based on a Signal>> has something to do with it.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template

Did you find this page helpful?