#createMemo recomputes but does not rerender

24 messages · Page 1 of 1 (latest)

random zealot
#

Bit new to SolidJS reactivity, coming from Vue. I'm trying to make the equivalent of v-show="boolean", which toggles display: hidden on the element.

Problem: In the childrenToRender memo, I build the children that actually get rendered by this component. As the console.log notes, this memo does re-run when it should because shouldRenderFallback evaluates to true, and then false. However, once it evaluates to false, the actual returned value list seems to never update.

I feel like I might be using these reactivity tools incorrectly, though I haven't found anything better for this use case in the docs

Usage:

const [loading, setLoading] = createSignal(true);
setTimeout(() => {setLoading(false)}, 300);

<Hidden when={loading()} fallback={<span>Loading...</span>}>
  <span>Loaded!</span>
</Hidden>
#

Component Code:

import type { Component, JSX } from "solid-js";

export interface HiddenProps {
    when: boolean | (() => boolean);
    children: JSX.Element | JSX.Element[];
    fallback?: JSX.Element | JSX.Element[];
}

export const Hidden: Component<HiddenProps> = (props) => {
    function resolveWhen(when: HiddenProps["when"]) {
        return typeof when === "function" ? when() : when;
    }

    const resolved = children(() => props.children);
    const childrenToRender = createMemo(() => {
        const list: JSX.Element[] = [];
        const shouldRenderFallback = resolveWhen(props.when);

       // correctly logs "true" then "false"
        console.log({shouldRenderFallback});
        if (shouldRenderFallback) {
            list.push(props.fallback);
        }

        list.push(...resolved.toArray());
        return list;
    });

    createEffect(() => {

        // This also feels weird that I have to do this, not sure how else to make sure this effect
        // re-runs when `props.when` changes
        on(
            () => props.when,
            () => {}
        );

        let list = resolved.toArray();
        const shouldChildrenBeVisible = !resolveWhen(props.when);

        list.forEach((child) => {
            (child as HTMLElement).style.visibility = shouldChildrenBeVisible ? "visible" : "hidden";
        });
    });

    return childrenToRender();
};
merry mantle
#

it should be

 let list = resolved.toArray();
        const shouldChildrenBeVisible = !resolveWhen(props.when);

        list.forEach((child) => {
            (child as HTMLElement).style.visibility = shouldChildrenBeVisible ? "visible" : "hidden";
        });
    });
}))
return childrenToRender();
#

but also

#

There is the Show component

random zealot
#

Yeah but Show actually removes the element from the dom, right? I need the element to stay in the dom, just not visible

merry mantle
#

I see

random zealot
#

so that it can run it's async code, and then once it's loaded, show it

merry mantle
#

Couldn't you just hide the parent of the children instead of each child

#
<div hidden={isLoading()}>
...children go here
</div>

random zealot
#

hidden={isLoading()} does this apply display: hidden?

#

ahh indeed it does

#

Originally I wanted to support multiple children, but I guess I could forego that

merry mantle
#

I wasn't sure if it was a one off use case, I just thought it could be a solution to the issue

#

By multiple children do you mean nesting your Hidden component?

random zealot
#

So that I could do this:

<Hidden when={loading()} fallback={<span>Loading...</span>}>
  <span>Loaded!</span>
<span>Loaded!</span>
</Hidden>
merry mantle
#

It could be a similar solution

#
const Hidden = (props) => {

return(
<>
  <div hidden={props.when}>
    {props.children}
  </div>
  <div hidden={!props.when}>
    {props.fallback}
  </div>
</>
)
}
#

Not completely sure if it would work

random zealot
#

<> brooo i forgot you can do this

#

1 sec will try

#

ahhh that works great thank you so much. Didn't even need any custom reactivity logic lmao