Reactivity issues with array items

Hey, I am having trouble figuring out how to create an effect that only runs when a property inside an object inside an array changes. I have a store that is a top level array and contains objects with an id and some content (which can be null). https://playground.solidjs.com/anonymous/b6728a0c-03a2-48e8-b8d7-27e483d22a8a In this playground I set up a minimal example. The effect logs the containers with content (where the content is not null) to the console. I want to log the containers when - The content changes - The content is removed - The content is added I don't want to log the containers when - An empty container is added - An empty container is removed Any help would be greatly appreciated!
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
8 Replies
Alex Lohr
Alex Lohr3w ago
It is almost always easier to wrap the store setter in your own capturing function instead of attempting to create logic that tries to run an effect on every change of the store.
SomeRandomNiko
I'm not sure if I understand correctly. Let me explain my actual use case. I'm building a navigation application which has some inputs for waypoints that you can add and remove. These inputs can also be empty. Whenever there are two or more inputs that are not empty I want to call my rest api to compute the route between all of the waypoints. If I swap an empty input with for example the "St.Ulrich" one, the api call should not be made because the order of non-empty inputs did not change. If I wrap the store setter with my own function how can I know if the order of the waypoints changed?
// Get context with a store getter and setter
const [transferForm, setTransferForm] = useTransferForm();


const fetchRoute = useAction(fetchRouteAction);

const debouncedFetchRoute = debounce(async (waypoints: Waypoint[]) => {
setTransferForm("routeLoading", true);
const route = await fetchRoute(waypoints);
setTransferForm("route", route);
setTransferForm("routeLoading", false);
}, 300);

// Get only the containers with waypoints
const containersWithWaypoints = () =>
transferForm.waypoints.containerContents.filter(
(container) => container.waypoint !== null,
);

// Get only the waypoints
const waypoints = () => containersWithWaypoints().map((c) => c.waypoint!);

// Effect only runs when new inputs are added, removed or when the order changes. But not when the content of the input changes :/
createEffect(() => {
const wp = waypoints();
if (wp.length >= 2) {
debouncedFetchRoute(wp);
}
setTransferForm("route", null);
});
// Get context with a store getter and setter
const [transferForm, setTransferForm] = useTransferForm();


const fetchRoute = useAction(fetchRouteAction);

const debouncedFetchRoute = debounce(async (waypoints: Waypoint[]) => {
setTransferForm("routeLoading", true);
const route = await fetchRoute(waypoints);
setTransferForm("route", route);
setTransferForm("routeLoading", false);
}, 300);

// Get only the containers with waypoints
const containersWithWaypoints = () =>
transferForm.waypoints.containerContents.filter(
(container) => container.waypoint !== null,
);

// Get only the waypoints
const waypoints = () => containersWithWaypoints().map((c) => c.waypoint!);

// Effect only runs when new inputs are added, removed or when the order changes. But not when the content of the input changes :/
createEffect(() => {
const wp = waypoints();
if (wp.length >= 2) {
debouncedFetchRoute(wp);
}
setTransferForm("route", null);
});
No description
REEEEE
REEEEE3w ago
In this case, you could just untrack the container.waypoint or c.waypoint
SomeRandomNiko
like this?
const containersWithWaypoints = () =>
transferForm.waypoints.containerContents.filter((container) =>
untrack(() => container.waypoint) !== null,
);

const waypoints = () => containersWithWaypoints().map((c) => untrack(() => c.waypoint)!);
const containersWithWaypoints = () =>
transferForm.waypoints.containerContents.filter((container) =>
untrack(() => container.waypoint) !== null,
);

const waypoints = () => containersWithWaypoints().map((c) => untrack(() => c.waypoint)!);
Still behaves the same way
REEEEE
REEEEE3w ago
wait, you do want the effect to run when the input changes? But anyway, as for stores, whatever you read is tracked. So in this case, since you're reading waypoint in containsWithWaypoints, it will be tracked. If you want another property to be tracked, you'll have to read it in the loop somewhere
SomeRandomNiko
Ok that makes sense thank you! As for checking if the waypoint order or the waypoints themselves change i "hash" their coordinates and compare that to the previous hash in the effect and it seems to work. I don't know if that is an ideal solution though
createEffect<string>((prevHash) => {
const wp = waypoints();
const hash = wp.map((wp) => wp.lngLat.join(",")).join("|");
if (prevHash !== hash) {
batch(() => {
if (wp.length >= 2) {
debouncedFetchRoute(wp);
}
setTransferForm("route", null);
});
}
return hash;
});
createEffect<string>((prevHash) => {
const wp = waypoints();
const hash = wp.map((wp) => wp.lngLat.join(",")).join("|");
if (prevHash !== hash) {
batch(() => {
if (wp.length >= 2) {
debouncedFetchRoute(wp);
}
setTransferForm("route", null);
});
}
return hash;
});
peerreynders
peerreynders3w ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
peerreynders
peerreynders3w ago
You don't even need a store