Svelte - how to derive a store which is returned from a function dependent on a store?

Viewed 387

The title may sound confusing, because the question is too.

I have a function - query(params: FooBar). This function returns a Svelte store, which I would like to base a derived store from.

let result = query("all users in group 123");
let userAvatars = derived(result, ($result) => { /* ... */ });

So far, so good - but the problem is that the query() call itself is actually dependent on a store value.

let groupId = getGroupId();
let result = query("all users in group " + $groupId);
let userAvatars = derived(result, ($result) => { /* ... */ });

The problem is that the parameter to query() only gets evaluated once and does not update when groupId does. I thought of two approaches:

1 - Making result itself a derived store:

let result = derived(groupId, ($groupId) => query("all users in group " + $groupId));

This doesn't work, because the function passed as the second param to derived() is expected to return the value directly, but I can only return the store.

2 - Making it a reactive statement:

$: result = query("all users in group " + $groupId);

Which doesn't work either, because the the result variable gets reassigned, and hence the userAvatars derived store doesn't react to the changes of the new store.

I am fairly certain it's possible by using the store API directly (e.g. .subscribe()) but I don't see a clear way forward, and it also might require a lot of boilerplate. Is there something I missed?

2 Answers

You can use derived stores (approach 1) by using its set method:

let result = derived(groupId, ($groupId, set) => {
    return query("all users in group " + $groupId).subscribe(set)
});

The second argument to the callback function, set, can be used to update the derived store whenever needed. If you return a function from the derived callback, that function will be called when the last one unsubscribes or when the callback is about to run again, which here is used to unsubscribe from the previous query. You can optionally pass a third argument to derived itself which marks the initial value.

Docs: https://svelte.dev/docs#derived

You can also use a readable store which has a set by default. In the readable you can subscribe to groupId (groupId.subscribe) and set the async readable result.

export const query = readable(null, set => {
  let unsubscribe = groupId.subscribe(id => set("all users in group " + id));
  return () => unsubscribe();
})

A Svelte readable store is very well suited to handle dynamic queries or fetches.

Related