Using 'createMemo' inside JSX (to render things which are only available in sections of UI)

I am trying to figure out how to createMemo for information, which is only relevant to some sections of the UI (which are rendered conditionally). Top-level (typical) createMemo will crash because the data based on which I'd like to compute it is not available in the entire component (but it is guaranteed to be available for some sections when those sections pass conditional rendering). Self-contained example Gist: in case array() is not null, we'd like to display UI about it, which includes its sum in two different places. Challenge is to do it without double-computation and code-duplication.
3 Replies
Igor Konyakhin
Igor KonyakhinOP4w ago
Code:
import { createMemo, createSignal } from "solid-js";

export function range(a: number, b?: number) {
if (b === undefined) {
b = a;
a = 0;
}
return Array(b - a).fill(null).map((_, i) => a + i);
}


export default function ReactivityExperiment() {
const minLen = -3;
const maxLen = 5;
const [len, setLen] = createSignal(2);

const array = createMemo(() => (
console.log(`Recalculate createMemo of array with len=${len()}`),
len() < 0 ? null : range(len())
));

return <div style={{ "font-size": 'xx-large' }}>
<button onClick={() => {
setLen(len() === minLen ? maxLen : len() - 1)
}}>-</button>
{len()}
<button onClick={() => {
setLen(len() === maxLen ? minLen : len() + 1)
}}>+</button>

{!array() ? (
<span>Select a task to see tests</span>
) : (() => {
console.log(`Re-render array-present case`);

// const arraySum = createMemo(() => (
// console.log(`Recalculate createMemo of arraySum`),
// array().reduce((a, b) => a + b, 0)
// ));

return <div>
Array: {JSON.stringify(array())}
{ array().reduce((a, b) => a + b, 0) }
{ array().reduce((a, b) => a + b, 0) }
{/* {arraySum()} */}
{/* {arraySum()} */}
</div>
})()}
</div>;
}
import { createMemo, createSignal } from "solid-js";

export function range(a: number, b?: number) {
if (b === undefined) {
b = a;
a = 0;
}
return Array(b - a).fill(null).map((_, i) => a + i);
}


export default function ReactivityExperiment() {
const minLen = -3;
const maxLen = 5;
const [len, setLen] = createSignal(2);

const array = createMemo(() => (
console.log(`Recalculate createMemo of array with len=${len()}`),
len() < 0 ? null : range(len())
));

return <div style={{ "font-size": 'xx-large' }}>
<button onClick={() => {
setLen(len() === minLen ? maxLen : len() - 1)
}}>-</button>
{len()}
<button onClick={() => {
setLen(len() === maxLen ? minLen : len() + 1)
}}>+</button>

{!array() ? (
<span>Select a task to see tests</span>
) : (() => {
console.log(`Re-render array-present case`);

// const arraySum = createMemo(() => (
// console.log(`Recalculate createMemo of arraySum`),
// array().reduce((a, b) => a + b, 0)
// ));

return <div>
Array: {JSON.stringify(array())}
{ array().reduce((a, b) => a + b, 0) }
{ array().reduce((a, b) => a + b, 0) }
{/* {arraySum()} */}
{/* {arraySum()} */}
</div>
})()}
</div>;
}
Here, two sections of UI compute the same summation code. This way it works as expected and never crashes, but there is code duplication and re-computation. Logical step is to createMemo with the said computation. But, in case the lines with createMemo are uncommented (even if arraySum() invocations are not), the code above crashes sometimes when array() becomes null. Specifically, when len() crosses 0 to -1 or 5 to -3: sometimes it crashes and sometimes it works as expected. What is the most robust and idiomatic way to achieve it?
Madaxen86
Madaxen864w ago
Maybe add to the range function : Array(Math.max(b - a,0)) so that is never negative. I‘d also put the sum inside a createMemo outside the JSX and just have a early return if !len() Also have a look at the Show and Switch/Match components (especially the callback variants because the also memo the checked condition.)
Igor Konyakhin
Igor KonyakhinOP4w ago
Thanks for a reply. Yes, I thought of those. Those would not crash. Alternatively, one may insert if (!array()) return 0 inside createMemo. But those all are kinda band-aid solutions with code and execution duplication (first we'll check for array() inside JSX, then we'll check for array() again inside createMemo. In case we have more "dependencies" more duplicated "safe guards" will be needed - not nice. In other words: I'm not looking for any way to get this specific example to not crash. The example is just for clarity. I'm looking for an elegant way for how to solve it in an elegant way (without duplication) in general. I looked into Show with a callback. It indeed works for this specific example:
<Show fallback={<span>Select a task to see tests</span>} when={array()}>{arr => {
const sum = createMemo(() => arr().reduce((a, b) => a + b, 0)); // somehow, array() instead of arr() also works!
return <div>
Array: {JSON.stringify(array())}
{ sum() } { sum() }
</div>
}}</Show>
<Show fallback={<span>Select a task to see tests</span>} when={array()}>{arr => {
const sum = createMemo(() => arr().reduce((a, b) => a + b, 0)); // somehow, array() instead of arr() also works!
return <div>
Array: {JSON.stringify(array())}
{ sum() } { sum() }
</div>
}}</Show>
is robust and I might use it in some situations. Surprisingly, even just using array() (which didn't work with ternary) works without crashes inside <Show>. This seems like magic. Isn't ternary ? : operator supposed to be compiled to the same thing by SolidJS compiler? At the same time, I couldn't make my own custom component <Scope> to provide a similar kind of magic guard. The main reason why this might often not work in general case is because oftentimes the desired check is not simply null vs not-null, but a check for a type of discriminated union:
<Show when={state().type === 'running'}>{ // Can't receive narrowed down sub-type in the callback?
}</Show>
<Show when={state().type === 'running'}>{ // Can't receive narrowed down sub-type in the callback?
}</Show>

Did you find this page helpful?