SOLVED: Trigger Side Effects on a Callbacks Dependency Updates
I'm connecting a live feed to solid and currently trying to apply a filter.
I filter the feed as it comes in, but if the filter function changes or a reactive value under its scope updates, I need to re-filter from scratch.
I'm not sure how to do this...
I'd prefer to not filter over the whole map for every insert, but idk how to detect those deep changes like "set_filter_user".
114 Replies
Heres an playground with a basic set up:
https://playground.solidjs.com/anonymous/9eef885c-1503-46d3-baba-a0ef80ea53ff
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
you can just jump to
live_filter
on line 200I can do this
https://playground.solidjs.com/anonymous/f6e10d5e-869d-4432-88ee-d131d122a17a
but now i'm just manually tracking deps
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
worst case scenario i can dumb it down and memo all entries:
https://playground.solidjs.com/anonymous/8f40836c-10d7-46d1-aaf9-ced2617ff7c6
but i don't really like it tho, requires post processing to filter and mem-foot print is larger than straight filtering.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Is this about the
ReactiveMap
or are you actually only interested in the final UI. Because if this is only about the UI this seems overly complex. I'd dumb it down a lot.
- For the UI just go with a store which holds an array of the entries that you want to display.
- Create two operations against the store:
- one that simply adds a single entry to the end of the array.
- another that replaces everything inside the store. Given the nature of your use case it's probably not worth bothering with using reconcile as in most cases filtering will only rarely have any common items between before and after filtering.
- Manage the feed history and the filtering at the edge of the reactive graph. There simply is no need for the entire history to live inside the reactive graph if you are not going to put it on the UI.
- When a new entry arrives AND the filter setting will let it pass use the add single entry op to add it to the store (otherwise just add it to the history but don't push it into the reactive graph; the history has all entries, it is the source of truth; the store on the other hand only has those entries which need to be shown)
- When the filter setting changes filter the cached history accordingly and use the replace everything op to replace the contents of the store.
The only reactive element is the store (driving the DOM updates) everything else is just mundane code outside of the reactive graph.
The only thing you are actually recreating are the arrays which hold the items. The items themselves are shared between the history and store so there is no duplication.I’m not worried about the UI so much as creating a very performant primitive. The feed code here is just a minimal example to demonstrate the trouble of detecting reactive changes within a callback (ie when we need to re-filter).
The premise of all this is to create a sort of “virtual table” so people are not doing redundant, full-scan filters across their whole application.
In reality, i imagine there to be multiple feeds and filters with incredibly volatile data (which is why i initially opted for Map) and this could all be in composables to drive heavier computations like physics engines and 3D-rendering.
It’s possible the complexity i’ve created is less performant than a simpler solution, so I’ll have to benchmark it.
i’ll try a store demo, add updates + deletions for all examples to simulate volatility, and figure out how to benchmark this ig…
HEAD-CANNON;
a callback in something like
createMemo
works as it can be immediately invoked to start tracking.
My filter doesn’t work as it cant be invoked without an input. thus its hard to capture updates in an effect.
in my last example, i bypassed this by tracking effects for each input on the map.
however… i wonder if i can simply require an args.length===0
run condition to start tracking before we ever receive a feed item to filter?
@peerreynders sorry to keep bothering you, but is this a possible approach? or does it run the risk of not encountering reactive variables due to some conditions and fail to track more generally? (I’m a little fuzzy on the details of tracking)
maybe i can turn the onInsert value into a signal. then its pre-defined and i can ensure its tracked appropriately on initHi! I'm kind of implementing something similar so I found reading your code interesting.
I think the point peerreynders said to
Manage the feed history and the filtering at the edge of the reactive graph. There simply is no need for the entire history to live inside the reactive graph if you are not going to put it on the UI.and some of the other points, seems to be what you are doing already As you have the
feed_cache
as the entire history and it isn't reactive but is just used to recreate the live_filter return value
I think I understand your point about your 'filter not working' because it can't be invoked without an input as you are dong on(filter, ...
. The problem is the effect doesn't get re-triggered when the set_filter_user
is called because that effect is listening to just changes to the filter
itself.
Now ... I'm trying to figure out a solution. My first thought was to just remove the on(filter
, part and just createEffect
on the inner function ... but that doesn't seem to work or I'm doing something wrong.OK I think this works.
https://playground.solidjs.com/anonymous/1bef2efa-31da-49ad-aeea-ab9dbf0b0de2
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Not sure if it is what you are aiming for.
The reason why this wasn't working (and also the old code partly I guess) was that
live_feed
was (and is) creating a new ReactiveMap everytime it was being run.
And the filtered
variable that was receiving the output from live_feed
wasn't reactive.
And thus the <MapIter>
which was listening to the previous instance of filtered
wouldn't get any reactive update.
So actually maybe another option is instead of creating a new ReactiveMap
everytime, you could keep using the same instance and make use of it's reactivity.
I've been trying to simplify the code.map was being weird so i switched it to store,
https://playground.solidjs.com/anonymous/d640cc13-e62a-4a02-aab4-7211fff53cb1
i believe this approach causes aggressive re-filtering on inserts (cuz its a derived signal)
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Yeah I think the map was being weird too. I also changed to store and doing Object.keys. But in my version reactivity is lost, trying to figure out what happened.
i don't think the <MapIter> expects the whole reactive object to be swapped on updates. thats just speculation tho
Do you know how to completely clear a store?
(Lol I'm still actually a noob)
my guess would be setSomeStore({})
Yeah, that was what I was doing at first.
After experimentation, turns out since the set function merges whatever you give it with the existing store, then setSomeStore({}) actually does nothing lol.
maybe setSomeStore(()=>{}) ?
Last night I found this guide, very good overview
https://raqueebuddinaziz.com/blog/comprehensive-guide-to-solid-js-stores/
A Comprehensive Guide to SolidJS Stores - Raqueebuddin Aziz
A guide on solid-js stores
yea i bookmarked that last night too 😂
So actually the way to clear a store (at least that I've found) is:
setSomeStore(reconcile({}));
Yeah, a guide like that one should actually be in the docs. (Right now I feel like the docs are written for geniuses lol)Alright, this is a cleaner version:
https://playground.solidjs.com/anonymous/e90d34b0-6f41-48f4-8783-3e7a51e0836f
I might clean it up a bit more, I think it is almost done.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
OK here here is the cleaned up version with some explanation comments
https://playground.solidjs.com/anonymous/39ff47e1-b544-4853-b41d-2eb022d00720
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
ah, I see you want to have a method that takes the filter_fn. Otherwise yeah, my comments in there explain my adjustments.
This was a great excercise for me. Now back to coding my own app, which using this experience should make it smoother 😄
But maybe @peerreynders will look at our code and say it is all the wrong way (lol)
OK, here I made just a small tweak and put the effects back in
live_filter
https://playground.solidjs.com/anonymous/83435aa9-add6-404b-a57c-335462eb7825
So live_filter
is kind of re-usable nowSolid Playground
Quickly discover what the solid compiler will generate from your JSX template
i dont think onInsert actually needs to be wrapped. am i mistaken?
i think the secret sauce of this whole thread is the dummy input to init tracking
Here it is working via map:
https://playground.solidjs.com/anonymous/ad450bbe-d67b-494a-be0a-9bd77ebcd685
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Thanks a ton @glassy !
i'll prob chuck an init value on the end of the function just so you don't have to worry about down stream typing (*updated)
I think the reason it needs to be wrapped is for when the filter itself gets replaced.
(
replace_filter
is called.)
I am trying to prove it but now I don't know how to call replace_filter
with a new one lol.
OK yeah, actually onInsert
doesn't need to be in a createEffect
because it is already calling filter()
Here I add a button to change the whole filter
https://playground.solidjs.com/anonymous/bfa2c832-30b4-4f1c-8251-7aba51eb5588
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
to change the filter I found out I need to do
replace_filter(() => newFilter);
instead of just
replace_filter(newFilter);
the () => newFilter
is needed because if you send a function to setSignal
it expects the function to return the new value to be set.yea did something similar:
https://playground.solidjs.com/anonymous/c82b0e5a-fec1-4064-b14d-020f54a1f769
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
i just replace immediately
lemme play around with urs
aha looking at your code again, I realized that actiually returning the
filter_feed
from inside the live_feed
function doesn't mean it is created more than once cause actually live_feed
is called only once. I misread the code at first.
So your code makes it even more reusable.changed to filter by sender as content updates at regular intervals, so if you dont start on the right milisecond you wont see anything
https://playground.solidjs.com/anonymous/c82b0e5a-fec1-4064-b14d-020f54a1f769
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
this was all a great help, this was at the edge of my understanding of solid
thx again for ur help!
Yes, I learned a lot too.
i wonder if theres a way to use the first insert to register the effect. then we can eliminate the need first the dummy
I think maybe put the onInsert and the refilter code inside the same createEffect might do it
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
I wonder if it would track properly if the filter changes while the feed is still empty
(ie. the new filter might depend on some signals that don't get triggered)
hmm I guess the register in the oninsert would handle it
I think the dummy version is simpler lol
i havent tested this, so there could be mistakes. but, just a thought
nice ... making it further generic
i think this is correct as the effect hasn't been registered yet, nothing would happen
I feel a lot more confident using stores now.
I used to (like just yesterday) think createMutable was so much more easy to use and didn't understand the value of createStore.
After reading that article above and playing with it today, it turns out actually pretty nice to use.
And the fact that you can keep the setStore method private actually does allow unidirectional data flow.
I also checked some of the most popular state management tools for other libraries, and actually stores + Solid is the simplest to use.
i love mutable for recursion.
when making a deep update in createStore via recursion, you have to append and pass keys to keep track of where you are. with createMutable, you can just traverse and directly mutate, its a good deal cleaner.
there are also some times where you need to pass setter and getter together, its can be cleaner to just use a mutable in those cases too.
those edge cases aside, stores are awesome. the versatility of the setter is crazy.
I just wrote this last night and am starting to try to use it
Anyways it is a pattern I'm going to start using as it allows components to easily access the store for rendering, while keeping all changes to the store controlled inside the StoreClass (due to the fact that setStore is a protected member).
Hmm, yeah recursion. Haven't gotten to that yet. I wonder if
produce
can be used with that.neat! encapsulating in classes can do a lot
also be sure to check out the flux primitive and native getters:
- https://primitives.solidjs.community/package/flux-store
- https://www.solidjs.com/docs/latest/api#getters
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
SolidJS
Solid is a purely reactive library. It was designed from the ground up with a reactive core. It's influenced by reactive principles developed by previous libraries.
interesting i haven’t thought about that approach
Thanks, will check it out
Yeah, produce makes using stores almost like createMutable.
Also the fact you can just setState({some: "partialState"}) and it will merge instead of replace means I figured out I will barely have to use setting with paths thing.
If it is more than 1 level deep, just use produce.
When I first read about stores, I thought setStore was crazy at first cause instead of normal javascript you have to use these weird paths. But produce makes it like normal javascript again.
Ah didn't know about getters in stores, could be useful
good to know thx
Ah flux .. looks familar like I used to write code like that in another life lol. I think I prefer just using TypeScript classes like my StoreClass pattern.
i really enjoyed the DX of pinia from vue https://pinia.vuejs.org/
the flux primitive is pretty similar. i know there are a bunch of agnostic state management libs that do similar stuff
Pinia 🍍
Intuitive, type safe, light and flexible Store for Vue
Yeah, I used to use vue too.
Vue 2 with class components.
When Vue 3 and react hooks became the craze, I started to learn Solid instead
Note to self, consider the situation that the cache is already populated. this may be the case for an initial get request that is then kept up to date by websockets
I was only focused on the UI, not creating some new reactive mechanism. My KISS solution doesn't even need a store.
https://playground.solidjs.com/anonymous/63664f46-20dc-42f5-8045-58029de0baa4
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
GitHub
solid-start-sse-chat/src/components/history-context/message-history...
Basic Chat demonstration with server-sent events (SSE) - peerreynders/solid-start-sse-chat
This is a very interesting approach
sees our reactive approach:
"look what they need to mimic a fraction of my power"
- peerreynders, probably
😆
Nobody's responded to this yet.
Also I have a small brain, so I have to accommodate for it in my code.
GOTO Conferences
YouTube
Kicking the Complexity Habit • Dan North • GOTO 2014
This presentation was recorded at GOTO Chicago 2014
http://gotochgo.com
Dan North - Agile Troublemaker, Developer, Originator of BDD @daniel-terhorst-north
ABSTRACT
Without rigorous care and attention software quickly becomes messy and unmanageable. Even with the best intentions entropy and complexity are a fact of life in growing application...
technology-hospice.md 😂
The key idea being that replace-ability is much more powerful than re-usability.
Is there a
reconcile
function for mutables?
Can you prevent re-renders when replacing a deep mutable object?That's exactly why you use reconcile with a store-to ensure the referential stability of the items being rendered which in turn minimizes the render effort.
I personally don't use mutables. Ryan cringes whenever he mentions them.
Actually, I just saw this from the article linked above:
So this wouldn't prevent re-renders for unchanged values?
Have a look at what is happening in the DOM (developer console, Elements view) on change. While it's not 100% reliable you should just see the new elements flash. I personally start with
{ merge: true }
.
Ryan has a distaste for mutables because they violate read/write segregation (and therefore don't fit the Flux view of the world)- they are simply a convenience that sacrifice RWS.So you're saying you can use reconcile with mutables, but they have this problem that they violate read/write segregation.
Is that right?
I've seen people echo the same criticism for mutables in this server and elsewhere, but I've also seen them being recommended for recursive components.
that was more an appeal to DX and maintainability, im pretty sure its still fine grain tho (afaik)
they may increase spaghetti if you abuse them, but i'd make the argument that RWS can also create spaghetti (ie recursion and passing setter/getter around)
just pick the right tool for the job imo
This is a bit old, but here is Ryan recommending
createMutable
for a "tree" component: https://github.com/solidjs/solid/discussions/499#discussioncomment-930195GitHub
efficiently render tree structures · solidjs solid · Discussion #499
import { render, Dynamic } from "solid-js/web"; import { createSignal, createState, JSX, For, produce } from "solid-js"; type Node = { id: number; children: Node[]; }; type Node...
Good to know!
if it were outright bad it wouldnt be in the framework, and i dont think theres any plan to remove it for V2 either
(scratch that https://discord.com/channels/722131463138705510/1245832940060147752/1246121125071687718, may be removed after all)
Discord
Discord - Group Chat That’s All Fun & Games
Discord is great for playing games and chilling with friends, or even building a worldwide community. Customize your own space to talk, play, and hang out.
It seems there's a general attitude in the community to recommend outright avoiding
createMutable
, but at the same time, if you look for it, you can find people claiming it's the best way to solve a particular problem.
Even the documentation warns against using createMutable
without saying anything about how sometimes it's the right thing to use.Yeah, It depends on how you structure your code anyways. I'm sure using stores if you pass around the setStore method then it can easily be spaghetti too. And if you write organized code then createMutable can work too.
But what clicked for me and made me see the value of stores is once I figured out that you can keep the
setStore
method private or keep it somewhere that no other code can access it, then you can control all changes to the store with good peace of mind.
At the same time you can expose the store
itself and components and other code can easily access the store.properties.whatever.data
directly which means the components are still as easy to write as normal mutables.
Then it is just the updating of the store
values that is different, which I then discovered produce
makes it as easy as mutables.
Anyways, I used to use MobX
and classy-solid
and Vue 2's reactivity which all work in the same way as mutables and I (and tons of other people) were able to ship good production code.
This pattern is what I like that stores can do. I could expose a mutable instead, but then anyone could modify it.
Otherwise I could put the mutable as a private member, but then when I want to return data to components I would have to write getter methods / or clone the output if I wanted to make sure no other code changes the data in the mutable.Would you still prefer to use stores even with recursive components?
I haven't tried that. OK let me think, you mean (I think I read somewhere) a Chat Message that can have children Chat Messages?
yea using stores in some sort of composable way is great to avoid prop drilling, set global states, or create encapsulated store instances
but you'll prob still have minor frustrations when the components directly reflect the shape of deep data (like nested trees) (but again i havent tried produce for that yet)
but you'll prob still have minor frustrations when the components directly reflect the shape of deep data (like nested trees) (but again i havent tried produce for that yet)
Something like this: https://ark-ui.com/solid/docs/components/tree-view
Tree View | Ark UI
A headless component library for building reusable, scalable design systems that works for a wide range of JS frameworks.
seen them being recommended for recursive components.It may well be that I haven't encountered a problem that needs them. But I've also observed that RWS is usually disliked by people who want "general reactivity"—these are same people who prefer the rendition of Preact Signals; an object with a getter/setter. In my view "general reactivity" is minefield of foot guns destined to give reactivity a bad name. Ryan's RWS philosophy on the other hand is one of responsible, mindful mutation. So when I plan the reactive portion of my code I always think of it as a uni-directional flow of change propagation: - starting at signal and store setters (perhaps
createAsync
consumers bound to cache
points)
- over various derived values and memos
- to finally terminate at the various effects (which of course includes the JSX).For trees, rendering isn't an issue cause the component root can just take the store root.
But doing modifications to it is the hard part I think. Gonna have to think about it cause trees are something I will need to implement in my app eventually too.
I think that with stores you'd have to keep the path for each sub-component of the tree so you could pass that to setStore later when you wanted to update.
I still think
produce
might work
And if starting at a deep point then
🤔
Each recursive component would have to keep record of it's path so you can start at right node when you want to do an operation on it.
yeah
on the other hand, with mutable you could just... do it?
Yes, tradeoffs.
lol exactly, just saw that
I did ask about stores within stores:
https://discord.com/channels/722131463138705510/722131463889223772/1246075106707636305
And it seems it is supported. So I'm thinking maybe that could be used for trees.
But again, that might still be kind of more complicated.
yeah, don't know if that is worth it just to keep RWS
It would be cool if there was a way to "slice" parts of a store
So it's true, createMutable might be the most straightforward way to implement trees.
I might still try that produce approach above though when I get to it (lol)
Then each sub-component would have it's own store, but they would only be "views" into a single store
if you want to avoid the deep mutation problem in a tree, you can use nested references that point to a Map()/Object
pseudocode...
you can also represent a tree as a list/map with whose nodes point to a parent or contain an array or children
really just depends wut ur doing tbh
Yeah, I saw someone suggest that before
But then wouldn't you have to keep the state in two different shapes?
I'm trying to think about how that would work
just have them separate and store a
children
property that points to the other nodes in the tree
Can you show how you'd render that?
anything that is not a nested tree requires more book keeping
Just keep the flat tree for reference in a context and you can do the recursive rendering normally but the components reference the flat tree to get their data
for completeness... the inversion:
Something like that
you can even avoid passing the children as props if needed and just pass the id along with the other props
That seems pretty cool!
Do you think this is a better approach than using
createMutable
?In my opinion it's almost always easier to use a flat tree rather than a recursive one
You can use
createStore
and do your updates easilyYou'd have to make sure that each "node" has a unique id tho
Another thing to note about
createMutable
is that it may be removed in Solid 2.0, although it will continue to exist in solid-primitives
oh thats news to me
Imagine I had a tree view component that shows list of People and a list of Animals
True, this completely depends on your data but you can probably figure out a way to create a simple unique id
you could have both a person and an animal with id 1
so you'd have to prefix that somehow
person-1, animal-1
something like that
very nice solution. but deleting an item is hard?
maybe each node needs to point both to children and parent
you'd probably just recreate the whole thing no?
without link to parent then if I want to delete item 'c' I'd have to scan every item to look for its parent
talking about this one
I kind of skipped the discussion so sorry if I missed something, was just looking at that and thinking about it.
yeah you can include a parent property too
I was just simplifying the example
Can you show how you'd delete a node that way?
if nodes can have multiple parents, then you'll need a different solution for identifying children/parents
No, I meant with the parent prop
You'd just remove 'c' from the parent's children?
look up the parent, remove 'c' from the parent's children, and delete 'c'
yup
gotcha
thanks!
then with both parent and children have to be careful they don't lose sync.
maybe only serialize one and generate the other on load
so at least the persisted data doesn't become broken
even with saving just one, could still have inconsistent data (eg. a non existing child or a non existing parent).
and that is the downside of not storing it as a straight tree.
however, in the end if it comes from a database, then the database stores it in the same way
probably the database just has a 'parent' column
but could be other ways. hierarchical storage in relational database is always something I have to read up on the different ways to do it everytime
so in any case, I guess having the in-memory store mirror how it's stored in the database would be a good idea
^
there are million tradeoffs... look up times, mutation costs, book keeping and denormalizing data, memory footprint, data volatility, shape of data and updates (may not have control), DX, etc. really just depends on the situation and requirements what is best.
Sorry to revive,
just cross-linking an update in case anyone should find themselves here in the future
https://discord.com/channels/722131463138705510/1252318635989405818