S
SolidJS•10mo ago
davidknp

Dynamically creating resources based on reactive state in Solid Store

What would be the right approach to solve this using solids reactivity? I want to achieve the following fetch the sources (the headings in the picture attached to this message) and then fetch the entities (what is below each of the headings) belonging to each of the sources. Inspired by the solid-realworld example I was trying to do something like this which doesn't work unfortunately: Code on Stackblitz
export function StoreProvider(props: any) {
let sources: any;
let entities: any;
const [state, _] = createStore({
get sources() {
return sources();
},
get entities() {
return entities();
},
});
sources = createSources();
entities = createEntities(state);
return (
<StoreContext.Provider value={state}>
{props.children}
</StoreContext.Provider>
);
}

function createSources() {
const [sources] = createResource('idplaceholder', getSources, {
initialValue: [],
});
return sources;
}

function createEntities(state: any) {
return createMemo(() => {
let entities = {};
state.sources.map((s: any) => {
const [resource] = createResource(s.id, getEntities);
entities = {
...entities,
[s.id]: {
get() {
return resource();
},
},
};
});
});
}

function App() {
const state = useStore();
return (
<>
<div class="sourcewrapper">
<Suspense fallback={'Loading sources...'}>
<For each={state.sources}>
{(item) => <ItemComponent id={item.id} />}
</For>
</Suspense>
</div>
</>
);
}

function ItemComponent(props: any) {
const state = useStore();
return (
<div>
<h2>{props.id}</h2>
<Suspense fallback={'Loading entities...'}>
<For each={state.entity[props.id]}>
{(item) => <div>{item.name}</div>}
</For>
</Suspense>
</div>
);
}
export function StoreProvider(props: any) {
let sources: any;
let entities: any;
const [state, _] = createStore({
get sources() {
return sources();
},
get entities() {
return entities();
},
});
sources = createSources();
entities = createEntities(state);
return (
<StoreContext.Provider value={state}>
{props.children}
</StoreContext.Provider>
);
}

function createSources() {
const [sources] = createResource('idplaceholder', getSources, {
initialValue: [],
});
return sources;
}

function createEntities(state: any) {
return createMemo(() => {
let entities = {};
state.sources.map((s: any) => {
const [resource] = createResource(s.id, getEntities);
entities = {
...entities,
[s.id]: {
get() {
return resource();
},
},
};
});
});
}

function App() {
const state = useStore();
return (
<>
<div class="sourcewrapper">
<Suspense fallback={'Loading sources...'}>
<For each={state.sources}>
{(item) => <ItemComponent id={item.id} />}
</For>
</Suspense>
</div>
</>
);
}

function ItemComponent(props: any) {
const state = useStore();
return (
<div>
<h2>{props.id}</h2>
<Suspense fallback={'Loading entities...'}>
<For each={state.entity[props.id]}>
{(item) => <div>{item.name}</div>}
</For>
</Suspense>
</div>
);
}
Davidknp
StackBlitz
Vitejs - Vite (forked) - StackBlitz
Next generation frontend tooling. It's fast!
No description
15 Replies
Brendonovich
Brendonovich•10mo ago
personally i'd abandon the store and just put createResource directly in the components that need them and then if i wanted to cache the sources + entities i'd swap out createResource for tanstack query
davidknp
davidknpOP•10mo ago
Good suggestion, in this case however I'm working on a highly interactive app where state is needed all over the place which is why I would prefer to keep this state global and colocate it with actions (which I have omitted from this minimal repro) as much as possible.
Brendonovich
Brendonovich•10mo ago
yeah in that case i'd use tanstack query that'll keep the same dx of createResource but store the data globally
davidknp
davidknpOP•10mo ago
I'm not sure tanstack query would help much in this situation since it would only be a replacement for createResource I would still have to dynamically create new queries in the same way, right?
Brendonovich
Brendonovich•10mo ago
the queries would be dynamically created as you render the components and if you want to prefetch the queries you can do that too but anyway one big problem i can see with your current impl is that you're creating resources inside a memo, so every time the memo re-runs the resource's data is being thrown out and refetching
davidknp
davidknpOP•10mo ago
I'm familiar with tanstack query and I understand this would be a simple solution for the example I have shown however the actual app is highly interactive meaning a lot of client side state, actions (incl. client side mutations) and derivations the actual data fetching part plays a very small role.
davidknp
davidknpOP•10mo ago
You're right createEntities()function has errors. Which is why I'm looking for alternative ways to solve this. As of right now the View is stuck on "sources loading".
No description
Brendonovich
Brendonovich•10mo ago
out of curiosity is your app a 'load a bunch of stuff then client only' or 'continually CRUD back and forth' type of app
davidknp
davidknpOP•10mo ago
It's something like a code editor so rather the first. There are some RPC calls when interacting with persistent state (e.g Filesystem) but other than that there is a lot of client side stuff going on.
Brendonovich
Brendonovich•10mo ago
ahhh gotcha i've been doing some playing around but honestly createResource is kinda yuck to work with outside of UI code. if you're not going to be doing component-led data fetching then i think managing the pending state yourself would be better Though even if you do manage the fetching yourself, if you can keep track of the promise and give it to a component then you can still trigger a suspense
bigmistqke
bigmistqke•10mo ago
You could use mapArray to loop over the object/array. Something like
const resources = createMemo(mapArray(
() => Object.keys(store.entities),
(entityId) => createResource(() => ...)
))
const resources = createMemo(mapArray(
() => Object.keys(store.entities),
(entityId) => createResource(() => ...)
))
davidknp
davidknpOP•10mo ago
Thank you, I'll try that @bigmistqke This works but it returns an array. How would you return an object of Record<EntityId, Resource> instead?
bigmistqke
bigmistqke•10mo ago
const resourceEntries = createMemo(mapArray(
() => Object.keys(store.entities),
(entityId) => [entityId, createResource(() => ...)]
))
const resources = () => Object.fromEntries(resourceEntries())
const resourceEntries = createMemo(mapArray(
() => Object.keys(store.entities),
(entityId) => [entityId, createResource(() => ...)]
))
const resources = () => Object.fromEntries(resourceEntries())
davidknp
davidknpOP•10mo ago
@bigmistqke Thank you it seems to work kind of and because it triggers the getEntitiesfunction correctly. But it seems it doesn't update as soon as the entities promises resolves. Do you know why it shows these "read" values?
No description
bigmistqke
bigmistqke•10mo ago
Can u make repro in playground? looked at it today https://playground.solidjs.com/anonymous/126b8a75-f860-41d3-a25f-2a34045ed6e4 u can do something like 👆 or https://playground.solidjs.com/anonymous/578acf1d-823e-4197-8cc1-eeeb503f7a89 forgot here to [0] the createResource as it returns [resource, ...]
Want results from more Discord servers?
Add your server