Fork me on GitHub
#re-frame
<
2016-04-13
>
lwhorton02:04:35

can anyone speak to a reframe strategy for dynamically created components? whats a good way to attach a dynamic piece of state to the app-db, and to clean it up after it’s not needed? do you have a parent component handle some :create-thing where you generate a random id? do you redispatch another event targetted towards a component and let it handle creating itself?

mikethompson02:04:05

@lwhorton: you appear to have a very component-oriented mindset. It's a bit like components are the "instigators" of things - its like they are the main "agents" in the architecture.

mikethompson02:04:52

But, in the re-frame/Reagent approach, components are simply reflecting/rendering the current state in app-db.

mikethompson02:04:35

They are not "actively" doing anything. They are not causing changes, and they are not "driving the app forwards" in any way.

mikethompson02:04:19

Instead a re-frame app "moves forwards" (changes) via events

mikethompson02:04:45

The handlers of events mutate app-db and that's how an app progresses.

mikethompson02:04:57

So, to finally answer your question, these "dynamically created components" of which you speak will only "appear" because of some state which appears in app-db. And that state in app-db was put in place by some event handler. And that event handler was run because of some event. So it is "events" that are the genesis of all change. So instead of thing about "components" being somehow active and causing things to happen, think instead of events happening and app-db changing, and the components simply rendering that change.

mikethompson03:04:24

So something like "cleaning up" will happen because an event happens, and an event-handler changes app-db (cleans up some data in it) in response.

val_waeselynck11:04:53

@lwhorton i have felt this pain too, here is my approach

lwhorton13:04:10

Thanks @mikethompson for the clarification. I still wonder, though - are we giving up modularity by having “someone else” manage our component data? My line of thinking is that as a component, *I* am the one responsible for the shape of my state, because I’m the only one who needs to understand it. The shape of the data I consume can come from a db somewhere and be formatted to fit my expected shape. However, I wouldn’t want to couple myself to my parent by requiring that a parent’s handler understand my innards. Does that make sense?

lwhorton13:04:42

To clarify, if I had a dynamic list of todos, and a button that says ‘create todo list’. :create-todo-list happens -> db gets updated in handler: {:todo-lists (conj (:todo-lists db) (create-todo-list))} -> parent/root runs a (for [[id list-state] @todos] …).

lwhorton13:04:34

That create-todo-list has absolutely nothing to do with the parent that handled the :create-todo-list — it doesn’t need to know what the shape of a todo-list is, and it definitely doesn’t want to have to change each and every time the todo-list needs to change state shape.

lwhorton13:04:16

My current solution is to have (ns todo-list.core) expose its own (create-todo-list [id] {:shape “of” :the “data”})

lwhorton13:04:25

Do you see what I mean about modularity?

nberger13:04:27

@lwhorton: I think I see what you mean, but re-frame philosophy is the other way around: the components don't need to know what's the shape of the data in the app db, and the subscriptions are responsible for reshaping data for consumption in the components

nberger13:04:07

Having said that, nothing stops you from defining the shape in the component, let's say "at creation time" and send that shape of data in the event, have the handlers to blindly take that data and put it into the db, etc... but then you have to be very careful if you are going to use the data in some other place different than your component

lwhorton13:04:23

So to put this in real terms… I would have a parent - lets call it PA, at a “top level”, responsible for maintaining a list of todo-lists.

lwhorton13:04:44

A button is pressed - and that button is rendered as part of PA, which dispatches :create-todo-list.

lwhorton13:04:12

A PA registered handler captures the event, then places somewhere in the db the shape of the todo list attached to a uuid.

lwhorton13:04:50

A PA registered subscriber detects the change to todos, then renders in a (for …) each todo-list, populating the individual lists’ state.

nberger13:04:20

what do you mean by "A PA registered handler"? Is your PA component responsible of registering the re-frame handler?

lwhorton13:04:38

A handler registered in re-frame by the PA component*

nberger13:04:27

so when is that handler registered? on :component-will-mount?

lwhorton13:04:49

Let’s just say that PA is always around. As part of the bootstrap process PA calls register-handler

nberger13:04:52

Ok, but it's confusing to me. The term component in the context of a re-frame app usually refers to a reagent component that takes data from the app db via re-frame subscriptions, etc... it doesn't register any handler.

nberger13:04:23

(it can be any React component of course, but it's usually created through reagent)

lwhorton13:04:58

That brings up a good point, though. I never really thought about the need (or if its possible) to do subscription/handler registering and deregistering.

lwhorton13:04:48

The react lifecycle events seem like the obvious place, but I would think that if a view isn’t around from the beginning, some parent is responsible for the handlers and subscriptions.

nberger13:04:19

Cool. I use to do that as part of some bootstrap process, the same as you are doing, the only issue maybe is that you are mixing a component there that I'm not sure what it is simple_smile

lwhorton13:04:06

To go back to the original point:

lwhorton13:04:10

How then, does one say “mark all completed” for a particular todo-list? Is the event somehow tagged with the lists’ id? At create-time is the list handed down a function to invoke (a plain callback) by PA which does the dispatching with the ID tagging?

nberger13:04:20

I use to think of it separate from the react components, and it usually runs before mounting any component

lwhorton13:04:11

My first thought (and this is probably wrong) is that a todo-list (when rendered the first time) would be handed an id to use to dynamically subscribe to something based on id.

nberger13:04:12

yes, I'd say the event is probably tagged with the list id

lwhorton13:04:51

(defn todo-list [id] (let [state (reframe/subscribe [:todos id] …)

nberger13:04:33

a todo-list can be handed an id so it subscribes to fetch the rest of the data, or it can be handed the todo-list data as props, so it doesn't need to get more data. that depends on how your app is structured

nberger13:04:13

yes, that looks good to me

lwhorton13:04:09

So for a :mark-all-completed id event, that handler would be part of PA, not registered by todo-list

nberger13:04:17

If PA is your bootstrap process, so yeah it's probably registered there. Definitely not registered by todo-list

lwhorton13:04:38

I guess at the highest level I’m really just trying to figure out where its appropriate to do registering of handlers and subscriptions in a way that is scalable.

lwhorton13:04:03

By scalable I mean you don’t have one giant parent that handles every single event and subscription related to everything in your app.

nberger13:04:45

I'd try to not think about it as a "parent"

lwhorton13:04:18

From the outside looking in I would like to think of “component” as a complete set of handlers, subscriptions, and view(s)… because that’s how I’ve always built systems to be modular and scalable.

lwhorton13:04:55

i.e. all that I need to do to have “this certain functionality” is to bring in that complete component… and hook up maybe one or two wires, but all-in-all its pretty self-sustaining.

nberger13:04:41

In some scenarios that makes sense, it's just that the view is already composed of component so it's confusing, at least to me. And also, in some apps that's not even enough: imagine if you have different views from different "higher level components", consuming the same data... so having the views mixed with the handlers and subs would be not enough

lwhorton13:04:40

I see. This has been very helpful. So if I might ask, how do you structure an app to avoid a big-blob-of-registrations in 1 or 2 files, and really thin reagent views everywhere else? How do you maintain a list of “available subscriptions” that a view can utilize?

nberger13:04:12

I have to run. I was writing a long comment about having todo.views todo.handlers todo.subs namespaces... but you could also do it the other way around: views.todo handlers.todo subs.todo... depending on the scenario I'd use one or another...

lwhorton13:04:10

Well either way, much appreciated. Thanks for your help.

nberger13:04:32

No problem, good luck with that simple_smile

lwhorton14:04:36

for those curious, after rethinking the above, I maintain modularity but also shift towards the re-frame “way" by having a /src/todo_list/core.cljs that contains all the register-sub, register-handlers, and initializing state fns necessary to operate a todo-list. a consumer of todo-list can simply dispatch [:todo-list/create-instance], which is handled inside of /todo_list/core.cljs itself where it assigns a uuid and places it into the database. the consumer can subscribe to :todo-list/instances, then render each todo via (for [[id props] @todos] ^{:key id} [todo-list id props]). By handing down this id at render-time, the todo-list knows “who it is” and can appropriately dispatch [:todo-list/some-event id args].

lwhorton14:04:43

Consumers don’t need to know any internals about a todo-list, they simply dispatch an event that creates one. The burden is placed on the todo-list implementation to maintain separate instances and tie events to ids. If anyone sees some downsides to this approach, feel free to let me know how dumb I am.

cork20:04:46

Has anyone had experience using RethinkDB with Re-Frame?