-
-
Notifications
You must be signed in to change notification settings - Fork 715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Global State #137
Comments
Yes, I been contemplating this move from a framework to library for a while. It is coming. In your terms, Except, that means you have to pass Anyway, a solution is coming. |
I should also point you to @darwin's pure frame fork: https://github.com/binaryage/pure-frame and his pull request #107 |
We could use React's |
That is exactly the plan. :-) |
Let me know if there's any way I can help -- I've done a lot of react |
@jaredly You've done some work on react-devtools, right? Well, if you have free hands for some fun work... This might be interesting for you. Maybe you could help me with a fresh devtools fork. First goal is to bring CLJS REPL to client-side seamlessly integrated with devtools javascript console[1]. But there are more ideas to provide great devtools enhancements for cljs development. |
@darwin kinda related ... at one point we developed a proof of concept debugger ... which allowed you to set breakpoints in devtools and then execute clojurescript code in the context of that breakpoint. The proof of concept worked. But it was all a bit rough. But we've never had time to get back to it and improve it. If anyone wants to hack on it, I';d happily make it public. |
@mike-thompson-day8 sounds cool, I think I will be able to support that quite easily. Javascript code generated by figwheel can be executed in the context of current breakpoint if I send it to devtools and let devtools execute it as if it was entered into console directly. I could have a look at your solution anyways. Thanks. |
@darwin if you can do that (execute in context of current breakpoint) then you are already there. No need for our heavier process. We have to fire the app up in Electron and then perform a sleight of hand to have two processes talking to the VM debugger (ours and the real devtools). |
@jaredly Hey thanks. I wasn't aware that Redux used Context in this way. On shallow reading, I can now see now they have the notion of a I'm just dwelling on how that might look in a Reagent/re-frame/clojurescript context. |
Hmm. Redux sure looks and acts like re-frame (and Elm, of course). But no mention of re-frame in the inspiration section. Middleware? subscribe? Etc? Huurrupphh - I want my 15 mins of github glory (15 characters of glory?). Oh well, moving on. |
@mike-thompson-day8 or convergent evolution? :) clojurescript has had a reeealy low profile in the javascript community so far (om notwithstanding), whereas elm gets talked about a lot. |
@jaredly re-frame was published about 7 months before redux, so there's not much "coevolutionary" about the two. And stuff like middleware is not a concept from Elm. And the redux readme has a certain comment and reference which is almost word for word out of the re-frame README (and it is sufficiently obscure that it could have only come from that one place). The Elm Architecture and re-frame were written up about the same time (late 2014). |
I'd been sailing along enjoying the convenience of re-frame's global registrations until today when I discovered devcards. Scoping stub handler registrations to single "cards" for isolating and testing components seems pretty critical. I guess I can proceed with the hope that a single stub handler per event type per page of cards will be sufficient, but this is pretty restrictive. (Or just make separate pages I guess.) Glad to hear something is in the works! Even a namespaced version of what we have now via some kind of context macro might work(?) e.g. (with-reframe-ns "cardfoo" (re-frame/register-handler :bar-handler ...) Thanks for re-frame! Redux may have some of the same ideas (and a suspicious literal translation of its name), but it still doesn't have CLJS. =P |
@mike-thompson-day8 what was the comment on the redux README that appeared plagiarized? Redux was what got me interested in FP and Clojure in the first place, and I was pumped when I came across re-frame to find that you had all the things I liked about Redux and more. |
Also, would love to hear best practices for using re-frame with devcards. Anyone have ideas of how to show a limited subset of the db as the app state? |
It's been a year since this issue has been opened and there's been lots of cool stuff coming out of re-frame, congrats to everyone being involved 🎉 I'm wondering what the "official stance" on this issue is and if there are any plans around the general issue. While many want to use Devcards I for one would like to be able to use re-frame's event loop but without the Reagent atom. I think one way forward could be trying to refactor some parts of the code base in a backwards compatible manner while separating or making it easier to separate global state. Would the re-frame team be interested in such contributions? |
@martinklepsch Part 1:
So Part 1 is fairly easy. But part 2 requires a bit more thought. In Part 2, when there are multiple instances of a Now, to make a Aside: something to be careful of: Anyway, this brings us back to registration. Where should I'm a bit attracted to the idea of The decision regarding Part 2, will feedback to Part 1. Usecases to guide us:
Anyway, just initial thoughts. |
Great to hear, thanks for the update and elaboration @mike-thompson-day8. I think what you outlined as Part 1 sounds great — and I think it would make sense to implement that even before we know exactly what Part 2 looks like for a few reasons:
On the relationship between a Storing all frames (and implicitly their registrar) in some place might be handy sometimes but I'm not sure it's hard enough to justify adding state to the framework. (There could always be Just for reference I think these are the bits of state that should be part of a
I think I will try to implement smaller chunks of what you outlined as Part 1 and then we can see how to proceed. |
Aside: @smahood points out to me (via another medium) that I'm using words/terms in a confusing way. The correct set of words to use are:
So, where above I was talking about |
@martinklepsch I can see two possibilities:
And how to do this in a backwards compatible way. Other thoughts? Preferences? |
My current understanding is along the lines of "each frame has a separate registry" and frames would expose a method of registering stuff (with their respective, embedded registry). This would allow adding (undo/register-undo! frame) I hope I fully understand the question... What the above would require is a (def reg-event-db (partial frame/reg-event-db the-global-frame)) Hope that illustrates my thoughts on the use case outlined above. |
To add to this list "Just for reference I think these are the bits of state that should be part of a frame:"
I mention this more for completeness, not that it needs to be solved right away. |
Not sure if this is useful or not, but I've been thinking about using the initialization step that loads your app-data (as in https://github.com/Day8/re-frame/blob/master/docs/Loading-Initial-Data.md#getting-data-into-app-db) as a distinct effect, and have that be the place where your frame is defined. There are almost certainly some use cases where this won't work and I have no idea if it's technically feasible, but if we can take advantage of the existing namespace tree (such that children NS are using the frame defined in their calling NS), then the backwards compatibility comes essentially for free for existing programs. Past that, I think if we can utilize interceptors or cofx (perhaps expanding the implementation but keeping the mental model consistent) then that seems to be an ideal way to do this - part of the context is the frame that will be used. It would mean extending them to work with subs and such, but it seems like this kind of dependency injection is what they are built for. |
My 2c and thoughts. I am still unsure this should go in here as while devcards is very good to have in your workflow, many maybe are not using it. I liked pure-frame and I wish it was a library actually. Totally random ideas: Could a key in the db for each devcards component be an alternative? Or an interceptor to save/reset/restore parts of the DB that are needed by each devcards page? I am just afraid that such a big change might complicate things in aframework that is disarmingly easy to grasp. This is definitely good to analyze deeply here in written prose (no Slack I mean) so that the brainstorming process is visible. Apart from that, good job as usual with |
@arichiardi I understand your caution. I feel it too. We write big applications using the current approach and, frankly, we find it works very nicely. I like the current simplicity (in both in library code, and program use). But, I'm open to working out how we tweak things in the direction of a library. But only once I get the overall picture right and see that it is worth it. By "worth it", I'm expecting to be guided by big improvements in these usecases:
Any other usecases I should consider? Is this the right way of assessing? (functional snobbery around globals is understood but is not a consideration :-)) @martinklepsch you have gone for "option 2". But I'm inclined more towards "option 1". I'd like a data oriented design. A |
@mike-thompson-day8 I absolutely understand and share your desire to have a design built around data. The Registry is a piece of state that holds all handlers potentially spanning multiple Frames. All Frames would have to be registered through that piece of state. Right now a registry's structure looks like this: {kind {id handler-fn}} If I understand you correctly "Option 1" would change this by introducing another level: {frame {kind {id handler-fn}}} Assuming this is correct let me make the following comments. (Using a numbered list so it's easier to refer back to individual items, not it imply importance or any other meaning.)
I talk about assumptions and unanticipated use cases above and really I can't think of anything particular that wouldn't work with a global registry. That said someone will find a funky way to break it and I'd rather have a malleable (or wieldy as Luke VanderHart put it) system than a system that irreversibly commits to something that may limit my possibilities down the line. If you've read all this, you probably deserve a fun video, don't you? |
Regarding use cases I would like to add: Everything in re-frame except for subscriptions is not tied to Reagent in a specific way. While I don't expect Re-frame to support other React wrappers any time soon it might be a good thing to keep in mind. I really like Re-frame's event model, the coeffects, effects and interceptors and being able to use that elsewhere (even with some extra fiddling) would be pretty cool. |
About the last point, I guess it is particularly true if we ever want to use it as reactive library for backend stuff. |
Hey quick update from Day8 team on this
Next steps forward:
Given that, we can't really look at merging in martinklepsch#2 into re-frame until we see example code on how it affects use cases. I can understand the desire to get some movement on this, but we don't want to pre-commit ourselves to one implementation before we see how it affects user code. In summary, we feel this is worth investigating further and after a refreshing holiday break, we'll be back to look at this with fresh eyes. I realise that's probably not what you're wanting to hear. If anyone is wanting to progress this further in the meantime, then working on providing React's |
@danielcompton, @martinklepsch Hi!
Reagent support context feature. (ns quester.util.url-helpers
(:require [reagent.core :as r]))
(defn provider [& _]
(let [helpers (atom {})]
(r/create-class
{:displayName
"UrlHelpersProvider"
:getChildContext
(fn [] #js{:urlHelpers @helpers})
:childContextTypes
#js{:urlHelpers js/React.PropTypes.any.isRequired}
:reagent-render
(fn [request-for & children]
(let [url-for (fn [& args]
(let [req (apply request-for args)]
(assert (= :get (:request-method req)))
(assert (= #{:request-method :uri} (-> req keys set)))
(:uri req)))]
(reset! helpers {:request-for request-for
:url-for url-for})
(into [:div] children)))})))
(defn wrapper [component]
"Higher-Order Component"
(r/create-class
{:displayName
"UrlHelpersWrapper"
:contextTypes
#js{:urlHelpers js/React.PropTypes.any.isRequired}
:reagent-render
(fn [& args]
(let [this (r/current-component)
url-helpers (.. this -context -urlHelpers)]
(into [component url-helpers] args)))})) It's like react-redux Provider and connect. |
@darkleaf Hey, thanks for outlining an example of context usage here, definitely helps getting an idea about how this could progress. The main problem I see with using context is that it effectively requires quite a bit of boilerplate to components even when they do only basic subscribing. One way around that could be to
I'm not a fan of new def-like macros usually but — assuming we want to use context / eliminate global state — maybe it's the only way? Rum's |
New macro is unnecessary. |
I've been investigating re-writing an Om app using re-frame, and I've landed here because (I think) I need a subset of this functionality. It's not a use case that's been mentioned yet, so there may be a better way of doing it - I'm coming from a few years using Om, so it may be clouding my understanding! Currently, all pages of the site (including user-authenticated ones) are able to render client or server side. On the client, global state is fine for this; on the server (node.js), it requires building a separate state per-request and feeding it into
Is there a way to achieve something similar with re-frame, bearing in mind that monitoring for changes and re-rendering isn't needed here, or would it need to wait until the entire state is decoupled? |
@gtebbutt if you are investigating the port to |
Thanks @arichiardi, that's useful to know. I'm looking at the options right now, so any info is helpful - whichever one works best for the project, it's good to keep up with the current landscape. Since the existing server API provides a fully constructed map of the initial state, it's starting to look as though the event system could potentially be sidestepped entirely on the server; basically, the render function from above becomes:
It should be perfectly safe, given the JS concurrency model, but the use of |
Hi guys, I created a fork of re-frame to explore some of the ideas detailed in this discussion. https://github.com/chpill/re-frankenstein/ A live example of what it can do: https://chpill.github.io/todos-re-frankenstein/ The readme should give some insight about the approach but please open issues if things are not clear (or just wrong!). I feel we can really make a version of re-frame that could work without global-state and be used with Rum (or other view layers!). Let me know what you think! |
Hello. I made a separate namespace to host a local state version of re-frame v10.2 some months ago. Basically, one creates a state which holds mostly all containers of re-frame (app-db, kind->id->handler, etc.). Then, inside the app, all calls to reg-sub, reg-event-X, subscribe, dispatch, will use the created state as first parameter. (defonce state
(-> (new-state)
(reg-sub :db (fn [db _] db)))
...
(reg-event-db .....)))
;; later ...
(dispatch state [:event-x])
(subscribe state [:db]) It appears to work well. I can use devcards with 2 re-frame applications on the same namespace/file. It may be useful for someone. |
Any new updates on this given the new React Context API? https://reactjs.org/docs/context.html |
re-frame is a really nice framework to use. Thank you much for providing it. I too would love to see re-frame turned into a library (as has been done by a few of the now outdated forks) and to provide support for not having the |
Let me start off by saying thanks for the time, thought and effort of those who have contributed to re-frame and this thread! Below I proposes changes that break from a lot of work already done. I hope it's taken as it's intended, which is to be constructive. ProposalWe should reconsider the solution of "lib / siloed app / separate states / db's / frames". I had the same initial inclination towards making re-frame a lib with siloed states. That being said, a core concept (and IMO a "superpower") of re-frame, or similarly redux, is the single global state where different parts of the system only pay attention to their domain. I believe the lib solution unnecessarily subverts that core principle. Instead we should continue to embrace the power of a single state and encourage thinking in terms of, what I call, the "instance pattern", where you have a set of events & subscriptions (you can call these "components", "modules", "frames", whatever... I call them "components") that operate on an instance under their domain (be it a whole app or an item) by receiving an absolute db path to where the concrete data resides. To support this we wouldn't need to refactor the core of re-frame but we would need to agree on a consistent pattern of passing path context to both events & subscriptions, similarly to @mike-thompson-day8's proposed part 2 frame solution. There would also need to be support for threading the context through subscriptions. Lastly, we'd probably want to build up a few more general utilities / conveniences for both events and subscriptions to improve the ergonomics of working with context paths. Use CasesApp / DevcardsOne immediate case I can think of where the "instance pattern" succeeds over a lib is when you have two apps / devcards that manipulate the browser url. Unless you want to give up that functionality, you'll need to merge it and at that point you really have one app with two instances that only look like their own apps. The fact that you have two apps on the same page is itself a strong indicator that you'll have interop so why encourage a pattern that effectively shuts the door on the option? Complex Components "Instances"There is a need for the "instance pattern" outside of the more dramatic devcards example. See Composing "complex" components or consider the simple case of "Items" where you have events & subscriptions (a component) that operates on items as well as individual items. Currently we tend to do an ad hoc solution of passing an item id as an argument Proposed Solution (Rough outline)
Closing notes
|
@daxborges my current thoughts on this can be found in the EP-002: |
@mike-thompson-day8 @daxborges I just want to jump back in here and add the server-side rendering situation to both of your use cases. Not that I think these solutions would be a problem in that context, just that it seems to me the likeliest use case where there will be many different It's arguably somewhat simpler (as the state probably only needs to be set once), but it is also a situation where the instance count may be high and it's critically important that data doesn't leak between instances. |
Thanks @mike-thompson-day8. I've summarized the comparison between your thoughts Problems Solved
Paradigm
Pros
Cons
|
Since re-frame is getting some renewed attention I wanted to point out that we've been quite succesfully using a fork based on @martinklepsch's frames PR at Nextjournal, which we're calling freerange. We're still working on and improving this, but are already using this happily on two separate projects. The big driver for us was being able to have devcards with their own isolated "frame". We've found the frames approach combines really nicely with React context. |
Thanks @plexus 👍 @mike-thompson-day8 has actually already pointed out freerange to me and we've had a look at the source code to inform some future work that will be coming on introducing contexts to re-frame. It won't be exactly the same, but its certainly helped our thinking so thank you! Both multiple re-frame instances on a page and reusable re-frame 'components' will be built on the context infrastructure. |
I am also maintaining a |
Has a plan distilled for re-frame instances that has maintainers blessing? The idea is 5 years old at this point and there are a few forks. I'm happy to do some work if there is an agreed path. Generally the approach seems to be this: #664 |
Thanks for writing re-frame! It's super awesome.
It would be nice to have the option of not using global state (the event queue, the
key->fn
handler functions atom,undo/redo-list
,id->fn
event handlers atom, globaldb/app-db
).I'm imagining a
(init-reframe-state)
that returns a map with all of the relevant bits. Then all of the public functions that usually just use the global state could take that state map as a first argument. e.g.(dispatch my-state [:event])
,(register-handler my-state ...)
etc.If you don't pass in your custom state map, then they would default to the current behavior.
Thoughts?
The text was updated successfully, but these errors were encountered: