SolidJS: Updating signal from within IntersectionObserver Callback

Issue

The following is a code snippet from a solid.js application. I am attempting to update the state of a top-level signal from within an IntersectionObserver’s callback. The state is being updated within the scope of the callback, but not outside.

Question one: how is this possible? If I can access setIds() and then manipulate the value – what would stop this value update from taking place outside of the scope of the callback?

Question two: does anyone know how I can update the state of the signal from within the callbacK?

const [ids, setIds] = createSignal(new Set<string>());

createEffect(() => {
    const callback = (entries: any, observer: any) => {
        const current = ids()
        entries.forEach((e: any) => {
            const intersecting = e.isIntersecting;
            const id = e.target.id;
            if (intersecting) {
                setIds(current.add(id));
            }
            else {
                setIds(current.remove(id));
            }
        });
        // NOTE: The following outputs the correct value
        console.log("This is the correct value:", ids())
    };
    const observer = new IntersectionObserver(callback);
    const elements = Array.from(document.querySelectorAll("p"));
    elements.forEach((element) => observer.observe(element));
    return () => observer.disconnect();
}, [setIds]);

// NOTE: The following does NOT update at all.
createEffect(() => console.log("This value is not being updated:", ids()));

The following is for people that prefer to test things out in real-world scenarios:

I have also provided a code block with an entire solid.js application that can be used for testing. The app provides a loop with 1,000 paragraph tags within an autoscroll element with a max height of ${n} pixels – creating a scrolling effect. Each paragraph comes with a(n?) unique id. It is my objective to have a constantly updating list (stored in a top level signal) that i can use to determine exactly which id’s are visible. So far, I have been trying to do this with an IntersectionObserver containing a callback. This is working within the callback however, the value is not being updated outside of the callback function.

import {
    For,
    createSignal,
    createEffect,
} from "solid-js";
import { render } from 'solid-js/web';

const App = () => {

    const [ids, setIds] = createSignal(new Set<string>());

    createEffect(() => {
        const callback = (entries: any, observer: any) => {
            const current = ids()
            entries.forEach((e: any) => {
                const intersecting = e.isIntersecting;
                const id = e.target.id;
                if (intersecting) {
                    setIds(current.add(id));
                }
                else {
                    setIds(current.remove(id));
                }
            });
            // NOTE: The following outputs the correct value
            console.log("This is the correct value:", ids())
        };
        let options = {
            root: document.getElementById("main"),
            threshold: 1.0
        }
        const observer = new IntersectionObserver(callback, options);
        const elements = Array.from(document.querySelectorAll("p"));
        elements.forEach((element) => observer.observe(element));
        return () => observer.disconnect();
    }, [setIds]);

    // NOTE: The following does NOT update at all.
    createEffect(() => console.log("This value is not being updated:", ids()));

    const arr = Array.from(Array(1000).keys())
    return (
        <div id="main" class="overflow-auto" style="max-height: 550px;">
            <For each={arr}>
                {(number) => {
                    return (
                        <p id={`${number}`}>
                            {number}
                        </p>
                    )
                }}
            </For>
        </div>
    );
};

render(() => <App />, document.getElementById('root') as HTMLElement);

Solution

Signals do not care what scope they are in, effect or not, so being in an intersection observer is irrelevant.

Signals are atomic values and you need to set a new value to trigger update. In your example you are not setting a new value but assigning it into a variable, mutating it and setting it back. Since Sets are reference values, basically you are not setting a new value, hence you don’t trigger an update:

const [ids, setIds] = createSignal(new Set<string>());

 createEffect(() => {
   const current = ids();
   setIds(current); // Not a new value
 });

You need to pass a new Set to trigger update:

const [ids, setIds] = createSignal<Set<string>>(new Set<string>());

createEffect(() => {
  console.log(ids());
});

let i = 0;
setInterval(() => {
  setIds(new Set([...ids(), String(i++)]));
}, 1000);

Answered By – snnsnn

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published