Creating a store with data returned from an async function
Simplifying greatly, this is what I would like to achieve:
I understand there is no way to get the return result of an async function in top-level code (this is within a
.ts
file).
Now I know some of you might suggest resources, however, I only need to use these async functions at app launch (aka once and never again), and they are all extremely fast (<10ms) so I do not need to use Suspense or add a loading indicator to my GUI.
The only solution I can think of at the moment is initializing properties like path
and view
with "dummy" values and then within an async-friendly initialization point such as onMount
, populating said properties with their real values fetched from the async functions.
Using dummy values is a really crappy solution in my opinion so I would be so grateful if somebody had a neat solution to this issue. Coming from lower-level languages like Rust, the lack of control over synchronization drives me insane in these frontend frameworks.
Thanks.75 Replies
You can use
createResource
and just not add Suspense
, I'm pretty sure that will just stop anything from being shown until they're done.
Coming from lower-level languages like Rust, the lack of control over synchronization drives me insane in these frontend frameworksTo be clear, with Solid you have pretty much complete control over how you synchronize your data and UI. You're absolutely able to do
homeDir().then(dir => setDirSignal(dir))
in onMount
or something.Since you want to use those values to populate a store, I think I'd fetch them in
createResource
in a component above the createStore
, and then use <Show>
so that createStore
isn't called until everything is loaded
I kind of want to isolate this data into a
.ts
file as it's global app data that will be used absolutely everywhere, including in functions in other .ts
files
You'll have to bear with me as I am coming from Svelte after not working with JSX or React in 2 years (with barely any experience even back then).Do you want
homeDir
or tab
available globally?I'll just provide you with a better simplified schema of the global state I want as my first example was not robust enough:
src/app/state/index.ts:
Since I need each tab to be part of this array of tabs, I can't isolate specific properties into resources or signals, or at least, I'm not sure how I would integrate something like a Resource into a store...
src/app/index.ts:
I'm using Tauri (I see that you're parting of the working group, neat!) and my application is pretty unique for a web framework like solid so it's hard to translate the expected web paradigms described in the docs into something that's usable for my app. I can't really just wrap things in components I need to be able to access this state from everywhere and I of course need it to be reactive.
I think I'd do the same approach I described above and make
tabs
accessible via context, refreshTab
can then take in the specific tab item as an argument instead of receiving the index and having to look it up itself
Context and Suspense worked great for us building SpacedriveHmm, I'll have to switch up my design paradigm then
I really appreciate the help!
No worries, hope you get it worked out
Also, you should be able to integrate resources into stores using getters
Ah nvm
You can't overwrite
path
with this setupYeah
One solution I've thought about is using the return value of
createResource
, then using the accessor and loaded things as usual, and using mutate to change it later
But once again using a resource is pretty redundant after initializationThis implies that the source of truth for
path
would be the resource, rather than tabs
which seems wrong. Most of these sorts of problems can be solved by thinking about whether the source of truth for the data should be
If you're gonna be reusing homeDir
for new tabs then not really, you need somewhere to store itWhat do you mean by "source of truth"?
Like the place that dictates 'this is the values of this thing'
With the getter example, even though
tabs[0].path
exists, the source of truth would be the resource you're mutating
Which doesn't make sense since each tab should be the source of truth for its own pathI'm not a frontend dev at heart but what I'm hearing is that it's discouraged to wrap data in a Store within a Resource?
Actually that does make sense because stores have their own proxying for reactivity
I wouldn't say discouraged, rather it probably doesn't make sense to do so in a lot of cases
And source of truths are relevant for the backend too, thinking about whether the source of truth should be some database, a piece of Tauri state, a value local to a function scope, or a remote machine
The fact that something as simple as using async code to initialize values in a store is such a complex thing in any framework is one of the reasons I really dislike doing frontend work :'/
So you could describe source of truth as just the true origin of the data?
Yeah sure
It's important to consider because down the line you're going to be doing transformations and derivations on that data, and if a modification is important you want to be applying it to the actual source of truth rather than some copy
Also I guess I meant the ambiguity around synchronization that comes with using frontend frameworks. In languages like Rust, it's very clear what order code takes place (ignoring async ofc but that's a whole nother can of worms).
Doing something like
let a = reqwest::get().await;
is pretty much the same as the <Show>
example I mentioned, since you're suspending the current future until the data resolves
And if you wanted global state that came from an async source in Rust I feel like you'd have similar synchronization issues
Like do you put it in a global static with OnceCell or Lazy, or a thread local, or have an optional field on a global struct and a task that receives from a channel and then updates the optional field, or no global at all and instead passing around some struct that has the prefetched data via function argumentsWell that is one hell of a sentence my friend
added some commas haha
At this point I think I'm having bigger issues because I am used to having a pretty clear separation between data and the gui code that reacts to that data
i mean what do you usually use?
Whether that's the right or wrong approach I may have to figure out
Svelte
I had this same issue with using async when creating stores and I ended up using the dummy data method
I mean it did its job and the app works fine
But it's not ideal and now that I'm trying out Solid to see if a rewrite in it would be beneficial I want to be as idiomatic as possible to avoid a bunch of refactoring pain in the future
Biggest pain in the ass in svelte was the lacked of nested reactivity
So you can imagine solid became pretty attractive
From how I see it, you can't get around the fact that you either a) don't create the list of tabs until you have loaded the prerequisite data, or b) use that dummy data until you have loaded the proper data. It'll happen in both Svelte and Solid and is just par for the course of dealing with async data sources
Having used svelte I also agree Solid is nicer haha
Yeah I'm hoping Solid is like Rust in that the DX and performance were given very deep thought
There's a lot of holes in Svelte when you do anything past the basics and that really contrasted with Rust.
Writing the backend was an absolute pleasure, but then came the frontend logic and it was just horrible in comparison.
Thanks, I needed the reassurance that there wasn't a better way, I'll choose one or the other.
Performance is absolutely a top priority, DX I'd say depends on what your're talking about. Solid provides a lot of primitives that let you build the way you want which could be a better DX if that's what you're into, but could also be harder if you're looking for a bit more hand holding
By great DX I mean this:
It's very rare I find myself writing what feels like redundant code in Rust (plus macros can supplant that a lot of the time)
Lmao
Yeah I'm willing to sacrifice ease of use for performance, we'll see how it goes
How's this for you haha
Yeah I need some time to wrap my head around the JSX way of doing this after using Svelte for so long
Anyways I really appreciate your help, made a big difference after dredging through the docs for so long. Have a good one
@Brendonovich Another question, would it be possible to have a reactive Solid store that is also bidirectionally synced with a Tauri disk store?
The code I prototyped above looks like it would produce an infinite loop...
if
tauriStore.set
triggers diskStore.onChange
then yeah that'll cause an infinite loop haha
Is the source of truth in this case the Tauri store, and the UI is just a reflection of it, or the UI store, where the Tauri store is just a backup?
Bc in the latter case I think I have an easy solution for youSorry, source of truth is for sure the Tauri store
Reason being I need to share this state between windows and have it automatically update the UI
In that case I don't think i'd have the
createEffect
I think i'd directly update the tauri store, and have the UI store be updated later by diskStore.onChange
How would I subscribe to changes on the Solid store then?
And if you wanted the UI to update immediately you could write directly to the store, which would then get overwritten by
diskStore.onChange
Basically the same as doing optimistic updates with an http apiI don't really want to manually write setters for every property
My hope was that I could create a catch-all that would avoid boilerplate
Wym setters for every property? You already have to write bindings for each field to the UI
Yeah, I'd like to be able to just assign a property in the Solid store and have that change reflected in the Tauri store
Not sure how binding works in Solid, haven't looked into that yet give me a minute to check the docs
By bindings i mean
onInput
handlers and stuffIs there no equivalent to something like svelte's
bind:value
on an input?Nah
Shit
Solid is simple, not easy lol
What's the reasoning behind that?
GitHub
Proposal: consider using 2-way bindable structures. · solidjs solid...
I know that, this might sound as a paradigm change, however inspired by this library, I've created a poc library for 2-way bindable structures, that might be considered for solid: This would ch...
one-directional data flow > two way bindings in solid land
Yeah that figures I've had some nasty bugs arise from two way flow
Here's something that might work
Only issue with one way is that it won't reflect on another window when it's changed
A prime example being 2 windows with a settings page open on each
Aha!
Fantastic I thought somebody else might have thought of this before
Sure it will, if a value in one window's store changes then it'll trigger
onChange
in the other window and update that window's store, which will rerender the UI
makePersisted
intercepts the setState
call rather than using an effect so you don't have the infinite loop problemNot if it's setup like this
<input value={store.preference} onInput={(event) => store.preference = event.target.value} />
I'd just watch out for synchronisation issues, one window's state will update instantly while the others lag behind
sure it will
ah i'm trippin
also do
setStore('preference', event.target.value)
yeah ofc that's just my svelte brain talking
lmao
unless you use
createMutable
I've heard pretty bad things about createMutable lol
include
makes it easy enough
either wayif your data is a tree it's great
otherwise avoid it
When wouldn't it be? You mean arrays v objects or sumthn?
I more just mean highly nested data, which trees tend to be. Passing down the setter and calling it properly can get annoying in those cases
Recursive data especially, since you have to design your components to work infinitely nested
Wait, avoid
include
or createMutable
?createMutable
hahaOkay good
So there's no real downside of just putting in the little extra work required for using a store/signal over
createMutable
right?If you want to avoid the footguns that
createMutable
can add then yeahIt appears that
@solid-primites/storage
has a sync api but it doesn't seem that they have tauri integrated with itimport { tauriStorage } from "@solid-primitives/storage/tauri"
got it wrong the first timeWith messageSync, you can recreate the same functionality for other storages within the client using either the post message API or broadcast channel API. If no argument is given, it is using post message, otherwise provide the broadcast channel as argumentI wonder if this works between windows with Tauri... I doubt it
tauriStorage
uses @tauri-apps/plugin-store
under the hoodI'm aware
I was rawdogging it before with svelte
Oh i thought you'd already implemented the
onChange
part
Oh nvm that does existI was just wondering if that might cause an infinite loop as well. I don't really know how it works under the hood.
Wdym
Ohh i get u
Looks like
set
will trigger onChange
in the same windowbummer
Good thing
makePersisted
is a wrapper around the setter
I'll give that a try later
Giving a new framework a try this late at night was a bad idea