Replies: 5 comments 5 replies
-
Quick follow-up to add, this is not just about performance. e.g. Solid's 'surgical' DOM updates are better at preserving UI state, such as keyboard focus, because it's not regenerating anything. Obviously React solves these problems too but I believe (might be wrong) this relies on special knowledge of specific elements like text inputs. Integrating 3rd party stateful UI components is an absolute breeze in Solid. e.g. I might want to implement a visual highlight when some field is updated by a remote user. I don't want to flash the whole object. Also ping @aboodman : ) |
Beta Was this translation helpful? Give feedback.
-
This looks scary to me because we have the same state in two different places and updated via two different mechanisms. It seems just like programming being what it is, we should be able to find some path where the two states diverge. Let's try... My first questions are:
It seems like if |
Beta Was this translation helpful? Give feedback.
-
Let's assume that the code that changes the store does the exact same thing that
|
Beta Was this translation helpful? Give feedback.
-
I don't have any
That would be a bug, so, something bad
Whenever a pull comes in, for every key that pull touches I fully reset that object in the Solid store to the state in the cache. Is that sufficient to address your concern?
The mutations in this system are all simple CRUD operations. None of them depend on the starting state. I can see that if any of them did - an incrementing counter for example - that would break the whole idea. If I need something like that I will keep the state entirely in replicache and use watch/subscribe. |
Beta Was this translation helpful? Give feedback.
-
Silly me for thinking you would ask a dumb question! This can indeed happen in my app, if there is a fractional-indexing key collision. However if I understand correctly, this question of yours was a lead-in to the concern about "optimistic mutations might not be correctly reverted", and I think I'm OK in that regard. Thanks for your help! |
Beta Was this translation helpful? Give feedback.
-
Following up with a Discord conversation with @aboodman.
A very brief look at how state works in Solid...
<Counter/>
componentUnlike React, the function runs once for each
<Counter/>
in my app. The calls tocount()
inside the div are "tracked" - Solid keeps track of dependencies such that the call to setCount will result in the text content of the<p>
being mutated directly. There is no dom diffing.Similarly with props. props.color is invoking a getter, which is also tracked. We say props is a "reactive object". Note that because
props.color
is actually a function call, if you unpack withconst {color} = props
outside of the div, you break reactivity. Only the code inside JSX is tracked.This also gives you reactive computeds for free - they are just plain js functions. The JSX calls timesTen, which calls count, so it gets tracked just the same. There is also
createMemo(() => heavyComputation(count()))
which gives you a computed that runs when (and only when) the state it depends on changes.Stores
Say I have a bunch of state:
I can instead do...
...and then use
state.a
andset('a', 100)
instead ofa()
andsetA(100)
But more than that, I can have deeply nested objects and they are reactive all the way down.
Read:
state.deeply.nested.field
Write:
set('deeply', 'nested', 'field', {newObject: "will also become deeply reactive"})
And that's about it!
set
has a bunch of super useful convenience features, but they don't change the model.Unidirectional flow
This is just a usage pattern. It's common to create a big store and either have it global, or make it available with Solid's context feature (very similar to context in React). We don't tend to make
setStore
(a.k.aset
in above examples) available, but instead do:The fact that
store
(read only) andactions
are separate is key to helping keep a unidirectional flow in the app.Replicache
Finally to the point. Here's how I keep my store in sync with replicache.
The state is duplicated - everything is stored in both the Solid store and in Replicache. This is clearly a downside...
The store actions update both - i.e. they call
setStore
andrepli.mutate.whatever(...)
On app startup, I use repli.query and tx.scan to get everything and populate the Solid store. Solid provides
batch(() => {...})
so you can do a bunch of updates and only trigger reactions at the end.The harder part, of course, is staying in sync with other users/tabs.
I have a custom puller and and onSync:
In the case of
del
ops, I might need to get at the object before repli deletes it. SowillApplyPull
just grabs a copy (tx.get(key)
) for everydel
.The sync of the Solid store happens in
didApplyPull
. It does everything inside a single read-transaction (query). It does the obvious thing: del - delete object in solid store, put - update object in solid store, clear - reset solid store. But it also needs to take into account the possibility that things have changed locally, due to un-pushed or in-flight mutations:del
: only delete it from Solid iftx.get(key)
returns null (i.e. check if it got deleted and then came back - new objects always have a new ID but deleted IDs can re-appear due to undo)put
: update the Solid store with the value fromtx.get(key)
, not the value from the put op. If it's gone, do nothing.clear
: fully reset the Solid store to the current repli state.As a last detail, in the
put
handler I do a field by field deep equality test of repli values to Solid values, and only callsetStore
on changed fields.Fingers crossed you are not going to tell me this is all fatally, irredeemably flawed 😬 🤣
Tom
Beta Was this translation helpful? Give feedback.
All reactions