Just reading https://github.com/reduxjs/react-redux/issues/1351 in reference from "what about hiccup", and mentions of concurrent mode. It definitely seems to me that an immutable store like clojurescript could provide would solve tearing issues with externals
it doesn’t if you always read from the external store in each component
redux is buggy (imho, don't take it as an attack on them please).
the tearing problem occurs if components read from an external store in an ad-hoc way. Imagine this:
(def store (atom {:name "lilactown"}))
;;
;; start rendering the component tree, components access store
;;
@store
;; => {:name "lilactown"}
@store
;; => {:name "lilactown"}
;;
;; Rendering is paused for some reason (suspension, or a higher priority update comes in)
;; Meanwhile, the store is altered
;;
(swap! store assoc :name "dominicm")
;;
;; Now we resume rendering
;; We re-use the components that were already rendered using the old value,
;; but the components that were left over will get the latest value - tearing
;;
@store
;; => {:name "dominicm"}
,,,
@lilactown what would not be ad-hoc, but would be external state?
I don’t know 😄
Haha 😄
It would be nice to have some way to control access to external state in a way that allows us to use snapshots or versioning. Obviously, this is very through the lens of clojure.
there would have to be some way to capture the state of the external store at the moment of render, and ensure that that value is used consistently throughout the component tree
there’s an experimental hook called useMutableSource
that supposedly handles this, I have not tried it
it has its complexities tho
Reading the rfc, the introduction looks perfect though
Seems like it could easily add a watch to an atom.
Oh, the rfc explicitly mentions redux. Hmm. That's cool.
@lilactown given this, and the alpha nature of suspense, I feel confident that you could avoid the cascading problem that's mentioned in https://github.com/Lokeh/helix/blob/master/docs/faq.md#what-about-hiccup with the changes that are coming down the pipeline.
yeah it’s very promising. I don’t know what the tradeoffs are yet by using useMutableSource
for all state
hopefully the Redux peeps will exercise that 🙂
runtime hiccup parsing will always be strictly slower, though, and I feel pretty good about the ergonomics of helix’s current approach to element creation using the $
and helix.dom
One problem I have is that they haven't exposed a way to describe equality. So a counter may be a good tool.
I hope that if people want to add hiccup, they can do that a la carte
Oh, absolutely. No denial there. I'm just trying to figure out just how bad I'm making things for myself :)
I think that the getVersion
would just use reference equality
What I need to figure out next is: how do I want to manage state? I don't like reframe, so I'm in no rush to clone it with hooks. But I'd also love to use someone else's work, if it existed...
the benefits to immutable data is that you can rely on a “fast no” - if the reference changed, the data probably changed!
Yeah, I guess I don't often do swap with identity!
the time this doesn’t work is when you’re dealing with external data e.g. data serialized from an HTTP request. if the data is the same as before, and you swap it into your store, then it can trigger a meaningless re-render of your app
but also, using =
would have to do a deep equality to ensure it was not changed, which might be bad for perf reasons
it’s all too hard!!
I'm assuming we can get this working with cursor functions too. But I also think that a counter that increments on non-equality could work out as a version.
=
would also fail for things like dates etc.
> I don't like reframe Sorry if it's a bit off-topic for this channel, but could you elaborate?
Perfect is the enemy & all that 🙂 Just needs to be "good enough".
it’s probably better to do the check of whether you should update the store, at the swap level - e.g. have some check that determines whether you should update the store given the current state
https://twitter.com/dan_abramov/status/691301224541503488 you can just call render with full state, no problem 😉
re: managing state, I’ve just been using local state mostly.
the only thing you really want an external store for are things like routing, external data cache, and maybe a few other select things like device status
for the majority of your UIs state, local state is way simpler and way easier to move around/test/understand performance of
routing on the frontend is an antipattern, but I personally love shared state. I built so many websites and what inevitably happens is that some state local to some component need to be accessed from some other component. I rather have a global datascript db and just do queries for react views and pass them down. I mean for small things, local state is 100% adequate, but once you get into heavy duty client side things with lots of data and especially stuff that needs to be synchronized, using local state quickly gets messy
@lilactown RE local state, how do you handle many things needing the same state? e.g. fetching data about logged in user. I'm fine with local state for things that are truly local, but it seems like I'm missing things like hot reload (I know about fast refresh). Ideally I'd have a fairly "pure" set of components on the whole, most of them taking their arguments as props.
yeah caching data is the biggest thing I’m focused on solving at work right now
we have a 50k LOC re-frame/reagent app that we are building new features in helix now
the only thing we’re currently actively still building in re-frame is data caching
I’m trying to figure out the best way to handle that without as much re-frame fanfare… it’s quite complex
re-frame’s event system is actually a pretty poor abstraction for handling processes like fetching data about a thing, because you have to handle so many branching logic paths that are scattered across events
fetch/cancel/complete/fail, and often you want to chain them
events are extremely low level for that kind of thing
:thinking_face: So you're trying to simultaneously redesign & replace re-frame? 🙂
But yeah, that's one of my big complaints. Scattering.
my goal atm is to figure out what a better abstraction is
then build the abstraction to interop with our current re-frame machinery
and eventually probably replace re-frame with something simpler
I’m still at step 1 tho 😛
but a good example is like, apollo’s graphql client basically obviates the need to use re-frame as the data cache if you’re all GraphQL
Yeah. I should perhaps investigate that. I did get quite excited by https://github.com/forward-blockchain/qlkit for a while. I do like the idea of having each component declaring it's required data and having that handled structurally as if by magic 🙂
if I had all the free time I would investigate pathom as a clojure-ific approach to it all
but there are some decently hard problems
fulcro provides a component-centric view of declaring external data requirements, but it also handles all state that way which I don’t want
I also think pathom & eql is huge in scope, learning all that is not too appealing to me. I've got stuff to do.
I want someone to add a lib to their helix project that gives them all the querying flexibility of pathom with an excellent caching/subscription behavior, just like apollo
yeah, there’s a lot of internal complexity to it that requires investment
lots of new terms to learn (took me like 3 days to figure out what an “ident” was) and setup required
fwiw, I think your goal above sounds wonderful! I would add the constraint that it should probably work with any competing libraries (hx/uix?) too. It would be nice to see less "re-" in reframe
One confession I have is that I've currently hooked up a global "user" in an atom using https://github.com/wavejumper/rehook But I'm not sure if it'll stick 🙂
The good news is that it allowed me to remove a window.reload() that existed due to the root component reading the account-type once on start...
I mean, Concurrent Mode / Suspense for data fetching / etc. is probably not coming out soon. I would be elated if it came out in 2020, but not expecting it.
we all gotta get work done.
that’s why I’m not ripping out re-frame any time soon on our legacy app 😂
I think the below captured it well. There's a lot of indirection introduced by a single high-level action. e.g. the code for getting the current user ends up in 5 places: • initial event that triggers it (click) • subscription to eventual data • user event that dispatches to http event • http event success • http event failure Doing all that every time I want data is a lot of boilerplate and work to juggle all the locations. Dependent on how/what you're testing, you may not feel the benefit of all that work.
Yeah, my "legacy" is small enough that I'm able to rewrite it onto whatever I want... I'm just not sure what I want!
am I too much a contrarian if I say that React should just dispatch actions and all state management should be outside of it? 🙂
I'm just happy to be rewriting JavaScript into Cljs.
something like a serviceworker
I think the goal should be to find an abstraction that provides a good enough interface between React and external state that it makes it mechanically simple to migrate to suspense or something CM-safe later
https://github.com/active-group/reacl this is pretty on topic to our conversation really 🙂 Your comment makes me think of that @ashnur I think it could be used both ways.
thanks! I am checking the links you share, but I am also working, so it takes a bit of time
I think it’s nice in theory, but it just moves the problem around. the benefits to allowing React to control your state is that it can prioritize it appropriately using concurrent mode
@lilactown You're right, but I think you can have both. Unless there's something I don't know about concurrent mode? Do they have some kind of prioritized queue for async state updates...?
yes, they do
Interesting. I wonder if there will be a point at which users can participate in prioritization... very likely given that redux is a priority.
yeah probably, there’s an experimental package https://github.com/facebook/react/tree/master/packages/scheduler
but there’s complexity that React handles for you by tying your state update to rendering to help avoid tearing 😄
I have some confidence in the react team making it possible to achieve things based on whatever API I want 🙂 I have to trust them about what they say is good in principle.
e.g. they're not (as far as I can tell) anti-external state. They want to support it. So I don't have to care. I'll just build whatever and make it fit with the advanced api they provide me for supporting concurrent mode.
probably the best approach
https://clojurians.slack.com/archives/CRRJBCX7S/p1587674788389400 I believe that react's scheduler/reconciler can perfectly well integrated with global state. I've read a lot of Fiber's source and a lot of discussions on github and I don't see any reason why global state couldn't be as performant as local state.
especially with fiber
looks nice, but something not depending on re-frame
first and foremost, basic state update triggers are the lowest priority afaik in concurrent mode, so just doing the naive approach already gives you most of the benefits
there is some new event system that's incoming too
Yup, that's "just" MapGraph on "top" of re-frame. But if you don't use the *.re-frame
machinery it still works.
The reactive part comes from reagent's reactions, though
cant find it now but https://github.com/reactjs/rfcs/blob/master/text/0147-use-mutable-source.md
isn't this exactly what you expect @dominicm?
Yeah, I was linked that above 🙂
sorry, I am slow > Both require a memoized “config” object with callbacks to read values from an external “source”. these could be the static queries I mentioned
wait, I just realized this is merged