Fork me on GitHub
#om
<
2015-10-19
>
josephjnk00:10:33

Hi, is this the right place for me to come for help using om next, or is this channel meant for project contributors?

josephjnk00:10:14

I'm new to om (and pretty new to clojure) and I'm trying to use devcards, but I can't figure out how to put an om component that has queries and mutators into a card

drcode00:10:20

yes, you can ask for help, I can't really give much help though, and dnolen is busy today

drcode00:10:41

but others may grok om next better than I do already

josephjnk00:10:42

great, thanks. I'll probably just pop back in tomorrow then, it looks like sunday night may not be the best time to find assistance.

dvcrn04:10:55

Short question, I have a mutator like this:

(defmethod mutate 'window
  [{:keys [state]} _ {:keys [w h]}]
  {:action #(swap! state assoc :window {:w w :h h})})
and transacting like this:
(om/transact! this
                  `[(window {:w ~w :h ~h})])
why do I get No method in multimethod 'dmedit-om.parser/mutate' for dispatch value: dmedit-om.core/window ?

tyler04:10:59

its the back tick in `[(window {:w w :h h})]

tyler04:10:27

that causes window to be namespace resolved

dvcrn04:10:11

I got that part, but why is it not able to resolve it correctly to my parser namespace? I checked the om-next-demo to see if I did something wrong but he's doing it the same way:

(defmethod mutate 'todos/clear
:onClick (fn [_] (om/transact! todos `[(todos/clear)]))}

dvcrn04:10:01

To clarify, the mutator is inside parser.cljs, the transaction inside core.cljs

gabe05:10:54

what is the intended way to set initial component state?

dvcrn05:10:28

I would just pass it to the component like (hello {:title "Hello, world!"})

noonian05:10:12

do you mean component local state?

sander06:10:10

dvcrn: so the parser only has 'dmedit-om.parser/window defined, not 'dmedit-om.core/window. things are easier when you make the ns explicit like in om-next-demo

sander06:10:22

just make it 'dmedit/window or something

dvcrn07:10:19

oh yeah, found that solution already and forgot to post an update here haha but thanks simple_smile

dvcrn07:10:35

going to try to change my app now to use the state like we talked about it yesterday

dvcrn07:10:31

but that brings me to the same question again. I am working with codemirror and when the codemirror component is mounted, it will push the actual codemirror instance into the state. Plus every time when you type something, that is being reflected in the state as well. With the example we had yesterday, I would build my state similar to my component tree, so these things would go inside :codemirror, probably. If I have a component X now and that component wants to use the text that the user wrote, it will have to access either the text state or the codemirror instance state which is inside :codemirror, but my rootcomponent would only pass :codemirror to the codemirror component and not to my other one. (ping @sander)

dvcrn07:10:21

I think the result we came up was that the mutator will have to add this key inside both state keys, right?

thheller09:10:57

@dvcrn: you should not put the CodeMirror instance into the state

thheller09:10:10

state is really for immutable data, which codemirror is not

dvcrn12:10:10

soo I went back to the very first tutorial and I think the difficult thing (for me) is to understand how I can request state through the subcomponent. The first ever example is using a counter that is requesting '[:count] and mutates on that. Now imagine I have a second component called Counter2. Everything same but it writes and mutates '[:count2] and is being mounted as a sub-component of Counter.

(defn read [{:keys [state] :as env} key params]
  (let [st @state]
    (if-let [[_ value] (find st key)]
      {:value value}
      {:value :not-found})))

(defmulti mutate om/dispatch)
(defmethod mutate 'increment
  [{:keys [state] :as env} key params]
    {:value [:count]
     :action #(swap! state update-in [:count] inc)})

(defmethod mutate 'increment2
  [{:keys [state] :as env} key params]
    {:value [:count2]
     :action #(swap! state update-in [:count2] inc)})

(defui Counter2
  static om/IQuery
  (query [this]
         [:count2])
  Object
  (render [this]
          (let [{:keys [count]} (om/props this)]
            (dom/div nil
                     (dom/span nil (str "Count: " count))
                     (dom/button
                      #js {:onClick
                           (fn [e] (om/transact! this '[(increment2)]))}
                      "Click me!")))))

(def counter2 (om/factory Counter2))

(defui Counter
  static om/IQuery
  (query [this]
         '[:count {:count2 ~(om/get-query Counter2)}])
  Object
  (render [this]
          (let [{:keys [count]} (om/props this)]
            (dom/div nil
                     (dom/span nil (str "Count: " count))
                     (dom/button
                      #js {:onClick
                           (fn [e] (om/transact! this '[(increment)]))}
                      "Click me!")
                     (counter2)))))
The important bit is
'[:count {:count2 ~(om/get-query Counter2)}])
I want to tell om to filter on the subquery, but with this syntax it would use the reader for :count2 and completely ignores the subquery. I could even write
'[:count {:count2 "om is weird"}])
and it would still have the same effect... anyone know what I mean?

sander12:10:36

the Counter2 query only specifies attributes, so its only function is to help normalize the data

sander12:10:24

there is no filtering here, only querying for data

sander12:10:50

not even sure it helps with normalization if there is no om/Ident involved

dvcrn12:10:06

but how would I collect that inside Counter, query for the data and pass it to Counter2 by actually using the query Counter2 provides

sander12:10:04

Counter just needs to call (counter2 (:count2 (om/props this))) instead of (counter2)

dvcrn12:10:49

that would pass the entire props and not just :count2. plus that only works because I use {:count2 ~(om/get-query Counter2)} inside my query which right now would be the same as just writing [:count :count2]. It is not using the query that counter2 provides

thheller12:10:58

@dvcrn: you need to give counter an identity

thheller12:10:28

not sure how to explain that, let me try some code

dvcrn12:10:26

thank you!

thheller13:10:49

@dvcrn: sorry, I have actually no idea how to do that. thought I understood it but apparently I don't

dvcrn13:10:58

ouch haha

dvcrn13:10:11

thanks for the try simple_smile

sander13:10:06

@dvcrn: try with a less generic read fn

sander13:10:09

and indeed, seems you don't need the subquery for Counter2 if you don't use normalization and you don't use remote fetching

dvcrn13:10:39

so you mean I should just do a very broad query on the root and pass that down?

sander13:10:31

if by "very broad" you mean "including both counters that you want to render", yes simple_smile

dvcrn13:10:27

mhmm guess that's the only way I can make it work for now

sander13:10:54

what do you want instead?

dvcrn13:10:11

to understand how I can do it with subqueries 😛

jannis13:10:41

@dvcrn: Ha, same here. I'm still trying to get my head around it. 😉

jannis13:10:18

I also tried the components / identity / normalization tutorial but it seems normalization is broken in master.

dvcrn13:10:02

I just did the entire example and works good for me

jannis13:10:54

Really? I get normalized data that looks like this: {:list/one [nil nil nil], :list/two [nil nil nil], nil {nil {:name Jeff, :points 0}}}.

jannis13:10:16

Which commit did you build om from?

dvcrn13:10:47

I used the latest tagged alpha

sander13:10:11

@jannis did you copy-paste the code or type it in?

sander13:10:21

really easy to make typos here in my experience

jannis13:10:58

I'll try building from the release tag

jannis13:10:28

No change. Odd.

gabe13:10:44

@noonian: I’d like to use component local state for storing query parameters

dnolen13:10:04

@gabe: not recommended, but also I don’t see why you would need to do that given the you can get them via the api

jannis13:10:01

@sander: @dvcrn: Right, it works fine when I follow the tutorial instructions. My project is using boot and perhaps something in that setup breaks things.

gabe13:10:10

@dnolen: I can get at the query params via get-query, but it’s a little inconventient

gabe13:10:46

@dnolen: I’ve been destructuring the query

gabe13:10:01

@dnolen: after finishing the quick start I added a slider to dynamically change the end query parameter. but i need to know the value of start so I can pass both to set-params!

gabe13:10:38

^in the AnimalsList section

gabe13:10:20

@dnolen: I’m guessing something like

gabe13:10:25

(defn get-params
  "Get the query parameters of the component."
  [component]
  {:pre [(component? component)]}
  (-> component get-reconciler :config :state :params))

gabe13:10:28

would work

jannis13:10:42

Oh dear. I've had so many problems with om recently, which was quite frustrating, and now it turns out the cljs version in my project was simply too old (1.7.48).

dnolen13:10:25

@gabe: all you’re sayings is that you want get-params

dnolen13:10:41

right so just say you want me to add that simple_smile

gabe13:10:02

I want you to add it 😄

gabe13:10:49

@dnolen: thanks! I just didn’t know if I was missing something

dnolen13:10:55

there’s lot of stuff like this missing that I’ve unintentionally skipped over

gabe13:10:03

if you had a good reason for leaving out

gabe13:10:05

sure sure

gabe13:10:41

@dnolen: I gotta say though. I’m really digging the level of indirection w/ the notion of updating query params instead of direct access of the global state

gabe13:10:44

good stuff

dnolen13:10:27

yes also I’ll probably start encouraging people use them to creates views over large amounts of data

dnolen13:10:31

it’s a simple perf enhancement

gabe13:10:55

your AnimalsList example already shows that implicitly

gabe13:10:57

in the small

dnolen13:10:35

@gabe: yeah will need to hammock that one for a bit this week, that’s really less about “Dynamic Queries” then it is about Unions in queries

jannis14:10:18

If I have something like {:items [{:id 1 :properties {:name "Foo" :count 2}} {:id 2 :properties {:name "Bar" :count 5}}]} in my app state, how would I query e.g. for :id and :name if I'm not interested in :count? Is there a way to do that?

jannis14:10:49

(I can make the query [:id :properties] which will give me everything and seems to ensure that :properties is included in the normalized data inside the reconciler.)

sander14:10:10

You can make your read fn just return the values you're interested in

dnolen14:10:24

as sander points out there is nothing automatic about how queries work

dnolen14:10:35

it’s up to you to interpret them same as it would be on the backend

sander14:10:36

But that's more work than just ignoring the values you're not interested in

dnolen14:10:32

if you think of queries & parsing as really fancy client side routing this all becomes a lot clearer how it’s supposed to work

sander14:10:10

And for more client-side automatic querying / 'filtering' there's datascript

jannis14:10:59

@dnolen: You mean "routing" not like URL routing but routing as in how data is routed through the application into components / the DOM and back?

dnolen14:10:14

@jannis I mean routing as in URL routing

dnolen14:10:23

that’s all parsing is except it’s multiway

jannis14:10:21

I have to admit I don't understand.

dnolen14:10:07

parsing is just routing

dnolen14:10:11

Falcor calls it the router

dnolen14:10:16

GraphQL is just a sugar for routing

jannis14:10:20

You mean read is like making get requests and mutate is like making post requests?

jannis14:10:17

And keys/operations passed to read/write are like URLs for these requests?

gabe14:10:57

@jannis: given

{:foo {:bar {:baz 5}}}

gabe14:10:21

foo/bar/baz gets you 5

dnolen14:10:15

you could even say Om Next is somewhat inspired by Ember.js by putting routing at the center of the design

jannis14:10:20

Hmm. I forgot most of what I knew about Ember after I started working with React. 😉

dnolen14:10:32

react-router is probably similar conceptually, not sure if you used that

jannis14:10:10

The identity tutorial doesn't use an atom as init-data and as the :state it passes into the reconciler. If I use an atom instead, the reconciler doesn't normalize its data. Is this expected?

jannis14:10:56

Ah, the tutorial states that if it's an atom the reconciler assumes it's already normalized. Ok

anmonteiro14:10:02

@jannis: or you could pass a denormalized atom and set :normalize to true

anmonteiro14:10:08

the reconciler accepts that option

jannis14:10:50

@anmonteiro: Ah, cool, that works. Thanks simple_smile

gabe15:10:54

@dnolen: (get-params this) is returning [:app/title (:animals/list {:start ?start, :end ?end})] on first render and {:start 0, :end 10} the rest of the time.

gabe15:10:54

well that would do it. thanks!

gabe15:10:04

@dnolen: works like a charm

gabe15:10:56

for anyone wanting to use sablono w/ om.next @r0man update react on his react-0.14 branch a couple of days ago

drcode15:10:42

The one I'm looking forward to most is the remote synchronization tutorial, I have to say I still don't have a good intuitive grasp for how queries are meant to be split between client & server state and how the are synced

drcode15:10:42

It'll probably be obvious once I think about it some more

tony.kay15:10:28

@dnolen: Curious about intended design in the large for om-next. Is there a composition story so that there can be "sub roots"? When I start to think about laying out a single-page app, I'm seeing top-level queries like: [{:menu-bar (om/get-query Menu) } {:toolbar (om/get-query Toolbar)} ...] which then means you write a pretty large recursive parser (not necessarily bad). I know you're working on the Union syntax for queries to deal with the fact that sub-components may want to swap in/out, but I am interested in knowing if that is the intended application structure. I guess there could be multiple top-level DOM elements with separate Root/reconciler/indexer, but then there would probably need to be a way for them to communicate with each other. No need to explain the whole plan, but we're trying to plan apps in the large and I want to make sure I've got the entire picture.

noonian15:10:59

@tony.kay: I've just been playing a bit with om next, but I've found you can write a read method for some 'virtual key' like :app/root and have that parse the selector as if it was at the root. I have no idea what implication that has on mutations though. This implementation has been working for me so far: https://www.refheap.com/110805

tony.kay16:10:34

@noonian: That is an interesting spin on the recursion. Restart at the top.

tony.kay16:10:07

seems like that would mess up the indexer.

tony.kay16:10:34

The indexer does a static walk of the data structure from root (via meta data on the query fragments)

tony.kay16:10:59

I guess as long as the Root still nests in :app/root in the primary query it would work

tony.kay16:10:53

I'm also trying to get my head around the caching and remote stuff, as one of our desires is to get a shared web-socket and server-push story going. So if anyone has comments on that I'd love to hear them.

tony.kay16:10:45

I've got a solution working nicely with sente and multimethods to multiplex the comms on websocket..but I have not even started working on how to make them work with om-next

dnolen16:10:46

@tony.kay: union is not about swapping in/out it’s just that something may show different types of things

dnolen16:10:20

@tony.kay: the design already supports “subroots"

dnolen16:10:46

absolutely nothing prevents a component from getting it’s own stuff unrelated to anything above it

dnolen16:10:59

not only does Om Next kill cursors it kills ref cursors too

tony.kay16:10:42

do we nest subroots via onComponentMount?

tony.kay16:10:54

seems you need a DOM element to hook it to

dnolen16:10:02

@noonian: that’s totally valid to do. Can’t mess up mutations because … normalization

dnolen16:10:10

all stuff already thought through simple_smile

dnolen16:10:20

@tony.kay: mixing metaphors here

dnolen16:10:38

subroot queries unrelated to anything about DOM

tony.kay16:10:38

I've been reading the source pretty heavily for the past few days. I don't see how a new root can get into the structure. I'd be more than happy to write a full tutorial on it if you can give me some pointers

tony.kay16:10:40

I see the parser getting called from add-root!, and I see how to follow the trail down...but I am stumped on getting a new subroot plugged in at an arbitrary point

tony.kay16:10:07

with its own parser

dnolen16:10:50

@tony.kay: sorry I misunderstood, you’re talking about the multi-root thing

dnolen16:10:09

yeah not going to support that in the near future

dnolen16:10:16

may revisit when everything else has settled down

tony.kay16:10:29

whew...glad I wasn't as confused as I thought

gardnervickers17:10:58

@noonian: What do you mean by “virtual key”?

noonian17:10:46

@gardnervickers: a key that doesn't actually exist in your state atom, but acts like it does exist and the value is the entire state atom. Like a symlink on a file system so if you query for [{:app/root [:app/title]}] and your state looks like (atom {:app/title "Some Cool App"}) your component will get passed the props {:app/root {:app/title "Some Cool App"}}

josephjnk17:10:36

I'm trying to use om next with devcards, and I can make a basic component that doesn't have a query appear in a card, but I can't figure out how to make a component that uses a query to work. I'm trying to embed the result of this tutorial into a card https://github.com/omcljs/om/wiki/Components%2C-Identity-%26-Normalization and I can get the RootView to render, but its subcomponents won't show up

josephjnk17:10:52

this is the code I'm putting in the devcard:

(defcard run-om-tutorial
  "Run the om next tutorial as a devcard"
  (let [tut-state (atom tut2/init-data)
        query (om/normalize tut2/RootView tut2/init-data)
        parser (om/parser {:read tut2/read})
        view (om/factory tut2/RootView)
        view-data (parser {:state tut-state} query)]
    (print view-data)
    (view nil view-data)))

josephjnk17:10:26

I see "List A" and "List B" headers, but nothing renders for the listviews inside of them

josephjnk17:10:33

am I going about this the wrong way?

dnolen17:10:45

@josephjnk: does devcards work with React 0.14?

josephjnk17:10:07

that is a very good question, I'm pretty new to this. I'll check

minimal17:10:29

since saturdayish

minimal17:10:09

@josephjnk: it should work from devcards 0.2.0-5

josephjnk17:10:44

ok. I was on 0.2.0-7 and just bumped to 0.2.0-8

josephjnk17:10:08

If it helps, this is what that print line outputs:

{[:list/one [[:person/by-name John] [:person/by-name Mary] [:person/by-name Bob]]] [nil nil nil], [:list/two [[:person/by-name Mary] [:person/by-name Gwen] [:person/by-name Jeff]]] [nil nil nil]}

josephjnk17:10:35

I think that I'm not correctly getting the subqueries? Since there's no points in there

minimal17:10:46

At least i’ve been using it fine but there may be unknown issues. Are you excluding react from devcards in project.clj?

noonian17:10:20

for devcards I've had success using the devcards dom-node helper and calling om/add-root! on the node that is passed to the function passed to dom-node

minimal17:10:46

Yeah i used that

josephjnk17:10:39

excellent, thanks! This looks a lot better than what I was trying

minimal17:10:53

I did the identity tutorial in devcards yesterday, i;ll just push it

josephjnk18:10:57

@minimal: I'm still doing something wrong, but I'll just start from your tutorial and work from there. Thanks for your help.

minimal18:10:30

no problem

a.espolov18:10:47

@dnolen: David, how to send custom params to mutate fn?

dnolen18:10:14

@a.espolov: just direct questions to the channel please

tony.kay18:10:30

@a.espolov: Use a map as the arguments to a call '[(do-thing { :a 1 })]

a.espolov18:10:47

@tony.kay: (let [t (get-custom-value)] '[(do-thing { :a t })]) ?

tony.kay18:10:03

you need to use syntax quote/unquote for that

tony.kay18:10:20

or don't quote at all, and just construct it with a call to list

tony.kay18:10:10

something like `[('do-thing {:a t })]

rads18:10:34

I think it can just be `[(do-thing {:a ~t})]

noonian18:10:32

only if your mutate function is already namespace qualified otherwise syntax quote with qualify it with the namespace its defined in

noonian18:10:02

so `[(app/do-thing {:a ~t})] would work

tony.kay18:10:21

So, I'm still struggling with David's comment "the design already supports subroots. Absolutely nothing prevents a component from getting it’s own stuff unrelated to anything above it". Does anyone see how to do that? As far as I can tell, all subcomponent queries have to be joined to the Root component through recursive use, or they are disconnected from the whole thing. The parser only runs on the root query. Recursion in the parser is an implementation detail of parsing. The reconciler has an add-root!, which is for the top level, and I see no way of telling the reconciler/indexer "I'm here" from some arbitrary subcomponent besides declaring a query that my parent pulls into its query.

tony.kay18:10:03

So if I want, say, some report that needs to pull a large clump of data over when it appears on the screen, I don't want it running as part of the top level parse. I guess I could patch it in via query parameters on demand...is that the technique?

dnolen18:10:52

there’s nothing about parsing that requires any of the previous context to apply

dnolen18:10:18

also I would keep the query bits separate from the components bits to keep the question clear

noonian18:10:48

that sounds like a dynamic query in the component that is conditionally showing the report component

tony.kay18:10:27

@noonian: Right, that is via query parameters, right?

tony.kay18:10:51

@dnolen: I see no way to trigger a parse/query on a component other than hooking it up to the reconciler

dnolen18:10:27

@tony.kay: I think I do not understand what you are asking

dnolen18:10:02

I would restate the question

noonian18:10:32

@tony.kay: I'm still waiting on the dynamic queries tutorial 😛. I have a dynamic query working but it feels a little dirty because I am accessing the app state directly and not through props or queries or anything to get the data I need in the query/query-params functions.

tony.kay18:10:40

@dnolen: Are all query fragments required to join all the way to the Root at the reconciler?

tony.kay18:10:54

that is my basic question

dnolen18:10:00

@tony.kay: this is what I was saying … no

dnolen18:10:18

if that’s your question this is has nothing to do components or the reconciler

dnolen18:10:16

you prove this to yourself just by using a parser on some data where subquery key reads some arbitrary thing from the state

tony.kay18:10:31

ooof. Sorry for being dense: 1. I understand that the parser is a standalone thing that is passed env -> key-> params and returns map (with :value key). 2. I understand that components can declare a query hooked statically to the component class...such that I can arbitrarily ask a component for the query fragment So, are you saying that in a render method I could do something like this: (render [this] (subcomponent (my-parser some-state (om/query Subcomponent)))

tony.kay18:10:08

cause that sorta seems like it doesn't exactly get hooked up to the whole works

jannis19:10:11

Does the reconciler re-normalize the state data after mutations if :normalize true is passed to it?

noonian19:10:48

@tony.kay: If you get the subcomponent's query in query-params or query you can compose it there instead of calling the parser yourself in render. By dynamically deciding which components to both render and compose their subqueries, and then by using the "virtual key to the root state" trick you can have arbitrary queries against the top-level from sub-components. I am doing this now but I believe it will become clearer once there is more documentation around dynamic queries.

tony.kay19:10:31

@noonian: Right. That was what I am assuming is the approach, however, the comment "Absolutely nothing prevents a component from getting it’s own stuff unrelated to anything above it" along with David's response to my direct question about things needing to reach all the way to the root confuses me....because the approach you are using always causes the top-level to be involved...the virtual root key just allows you to "loop back" to the top of the real state.

dnolen19:10:26

@jannis: mutations always work on the normalized data

dnolen19:10:13

@tony.kay: that pseudo-code doesn’t make sense to me. I don’t see how this is related to your question

noonian19:10:30

@tony.kay: thats true but that was my desired behavior hehe. I think that its a very case by case basis and that approach makes sense for a page-like component where you have no idea what each page wants so give them the top-level. Alternatively you could let the sub-components not access the top-level by default but if they needed to they could just query for the root themselves.

dnolen19:10:08

@tony.kay: the query is just about getting a tree for the UI

dnolen19:10:18

there’s no such thing as “looping back” on the state

dnolen19:10:24

state is normalized, it’s flat

tony.kay19:10:04

I was commenting on noonians approach (loop back to the top via his parsing trick)

jannis19:10:47

@dnolen: I have some code that polls a backend for data and then tries to insert it into the app state via (om/transact! reconciler ...). However, the data it receives from the backend is not normalized. Do I have to e.g. dedup/normalize this data myself before I can insert it into the state in the mutation?

noonian19:10:34

yeah it doesn't change the state but the props you receive present the illusion that the state is a self recursive structure

dnolen19:10:07

@jannis: you will have to normalize yourself if you’re going to use transact, there’s also merge! which probably needs some tire-kicking

tony.kay19:10:49

but this "tree for the UI" is peppered with persistent state...perhaps I am misunderstanding the picture. I assumed that any data you want in the UI is coming through these queries.

tony.kay19:10:42

and the parser is working out which is "persistent" and which is just UI-related

jannis19:10:03

@dnolen: Ok. What does the delta to be passed to merge! look like? Is it just {:objects [...]} if I want to update :objects in the app state?

noonian19:10:37

I think of the parser as taking the app state and a query and building the tree of props that will get passed to the tree of rendered components. So thats two trees that are distinct from the app-state tree

noonian19:10:32

so only the state atom is "persistent" as you say, and the tree of data from the parser is just built up in order to render the components

tony.kay19:10:16

Yep, that is my understanding as well. I'm going to get some food and consider how I might do a better job of asking my question...or perhaps the light will come on. I've got the core functionality. I'm more on to what you were saying about dynamic queries and choosing to render things.

tony.kay19:10:52

I'm almost certainly making it more complicated than it needs to be

noonian19:10:10

Cool, thanks for letting me talk this out as well heh.

tony.kay19:10:28

Oh...nice to hear my questions are helping simple_smile

tony.kay19:10:56

I'll be back in an hour if you'd like to continue bouncing ideas

dnolen19:10:10

@jannis either a map of “refs” or a mergeable result

dnolen19:10:47

a ref is a single item update, a mergeable result generally needs to have the same shape as the total query if not all the same keys

dnolen19:10:13

single item updates work great from what I can tell

dnolen19:10:19

the tree merge bit needs tire-kicking

jannis19:10:25

I'm not sure how either would have to look like when being passed to merge!. Would a single item update be something like {[:person/by-name "John"] {:name "John" :points 5}}? Or completely different?

dnolen19:10:22

that’s it

jannis19:10:07

Nice. I'm very slowly feeling less lost. 😉

jannis19:10:15

How about a mergeable result? [{:person/by-name {"John" {:name "John" :points 5}}}]?

dnolen20:10:09

so that won’t work

dnolen20:10:14

you have a wrapping vector there

dnolen20:10:32

if you unwrapped that should work

jannis20:10:58

Before we dive into this: the single item update would only work if "John" already exists in the state, I assume?

jannis20:10:22

Hm, no, it doesn't matter. If I do (om/merge! reconciler {[:person/by-name "John"] {:name "John" :points 0}}) I get an error: Invalid key [:person/by-name "John"], key must be ref or keyword

jannis20:10:23

Whereas (om/merge! reconciler {:list/one {:name "John" :points 0}) does the job.

jannis20:10:13

But that's not an update via a ref. 😉

noonian20:10:38

@dnolen: could you explain the difference between transacting on a (possiby nested) component or transacting on the reconciler? It seems like the result value of a mutation triggers rendering with different props depending on where in the UI tree the transact! is called.

noonian20:10:16

and is it idiomatic to transact on the reconciler from within a component using om/get-reconciler?

tony.kay20:10:14

@noonian: OK...lunch helped. I see where I was confused. I talked through it with coworkers and I see why my questions were confusing...your comment on the UI tree helped it fall into place. The app state is just some bag of data that changes over time. The UI tree is how I want to view that app state at any given time. Dynamic queries give us the flexibility to morph the UI in arbitrary ways, which may involve arbitrary queries to the server at any time (which if they update app state will trigger re-renders).

tony.kay20:10:16

I was trying to tie the component queries to server queries (directly instead of indirectly).

noonian20:10:17

@tony.kay: yep that is my understanding also

dnolen20:10:49

@jannis right the thing doesn’t exist I’m assuming in the error case

dnolen21:10:00

@noonian: transacting on the reconciler is just a convenience for “remote control” - i.e. mutations from processes that are not a triggered from Om Next components

jannis21:10:54

@dnolen: No, the error happens even if it exists.

jannis21:10:29

@dnolen: I'll play with this more tomorrow and try to come up with some minimalistic demo code.

noonian21:10:06

I'm finding that when I transact on my component instance it causes the component to re-render once with nil props and then stop rendering changes. It doesn't happen when transacting on the reconciler. Its probably a bug on my end but I

noonian21:10:13

I'm having trouble pinpointing it.

jannis21:10:09

I'm also not sure how merge! could be used to remove things from the state. If merge! is used to update the state based on data retrieved from a backend (e.g. a REST service), things might disappear.

dnolen21:10:35

@jannis leaning towards just using nil for property deletions, seems fine to me far as UI problems

dnolen21:10:51

for things deleted from vectors there’s nothing to do, it’s either there or it isn't

dnolen21:10:02

same for insertions, prepends, appends

dnolen21:10:07

all that stuff gets sorted out by React

jannis21:10:03

When your REST service simply serves a list of objects, how do you know which old ones in your app state to set to nil via merge!? Query the current state, check which ones are no longer present in the list served by the backend, assemble a merge! delta accordingly?

dnolen21:10:28

you’re talking about a different problem, that’s local cache eviction

dnolen21:10:53

will probably provide something automatic for that

dnolen21:10:15

if no components are referring to a particular piece of data we’ll probably evict after some time T

jannis21:10:49

Ah, that's an idea. Cool