Fork me on GitHub
#untangled
<
2016-04-29
>
tony.kay16:04:05

With respect to configuration: I'd prefer different config components if you have specific requirements. If there is a problem overridding the one we supply, then a PR on that is fine.

tony.kay16:04:21

I guess if what you are wanting is to be able to put things like :env/DB_URL into a config file and have those override at start that would be ok. I don't remember, @adambros , if we allow putting the application config in resources. We don't want that for our purposes, but loading it from classpath is technically trivial...so we could allow (if we don't) the production config to exist in resources. I'd want a little of the protection of "you have to say where the config is (e.g. -Dconfig=)" so it cannot be started in production accidentally without a config.

vmarcinko18:04:29

hello, I have a Root component that initially displays people well from app state that looks like this:

{:people [[:person/by-id 1] [:person/by-id 2]]
and the component is defined by starting with:
(defui ^:once Root
  static om/IQuery
  (query [this] `[{:people ~(om/get-query Person)}])

vmarcinko18:04:36

When I click on Delete button that is rendered as part of Person component, my app state changes well, and I can see that using (log-app-state) but my Root component doesn't get rerendered. Anyone knows why rerender doesn't happen?

ethangracer18:04:36

@vmarcinko: are you defining the delete function in Root or in Person? If any delete function for a child isn’t defined in it’s parent, the parent won’t be re-rendered

vmarcinko18:04:53

i define it in Person (child)

ethangracer18:04:21

yeah, that’s the reason, you’ll have to defined the delete function in the parent and pass it to the child via om/computed

vmarcinko18:04:38

thanx regardless simple_smile

ethangracer18:04:52

since om/transact! triggers a re-render from the component passed in as the first argument

vmarcinko18:04:15

i'm maybe accoustomed with reactive libs that notice change in mdoel and re-render all components that depend on some model below

vmarcinko18:04:26

thus my reasoning

ethangracer18:04:06

yeah, I think the issue is that you’re not technically changing the react key — you’re removing it

ethangracer18:04:14

so om has to compensate for that

vmarcinko18:04:32

is om/computed and callbacks frequently used for that purpose, or there is some other way to re-render parent?

ethangracer18:04:42

but I don’t know much about React so I’m kinda making things up that sound like they make sense 😬

ethangracer18:04:58

that pattern is the expected pattern for both om and untangled, yes

tony.kay18:04:28

@vmarcinko: So you need to understand the difference between Om and others. By default transact indicates that the subtree being acted upon should be re-rendered. That avoids the need to even look at the whole tree. Transact can specify follow-on reads that indicate other things should be re-rendered by what they query. Eliminates a lot of overhead.

tony.kay18:04:25

So, technically, you can say (transact! this [(f) :parent-prop]) in the child and the parent will re-render; however, this breaks composition, because your child now acts upon knowledge of the parent

tony.kay18:04:28

The "magic" of "I change something and the framework does a bunch of work to figure out what to do" kills your frame rate. Reactive atoms either re-render huge trees, or need to be embedded all over the place.

tony.kay18:04:58

The abstraction of "trasactions should affect things locally" should never re-render a parent, because the child should not know about the parent. Thus the recommendation that transacts that affect the parent should be written there, and passed as callbacks. This is the REact way in general: (dom/button #js { :onClick ... } "Click Me")

tony.kay18:04:10

you would never want button to be hard-coded to know what the parent should do

tony.kay18:04:33

computed is an unfortunate requirement that has to do with re-rendering not killing callbacks.

tony.kay18:04:42

(my-child (om/computed props { :onClick ... })) instead of (how it was originally) (my-child (assoc props :onClick ...)). The latter doesn't work right for re-rendering reasons

tony.kay18:04:05

The abstraction is then continued to "you should not need to know which components need to be updated, and we also don't want to do a bunch of dirty data checking"...so instead, say what data (abstractly) is affected, and Om will find the components that query for that and re-render those....that is the follow-on read

tony.kay18:04:58

(transact! this '[(f) :people]) = Do mutation f, then re-render anything that queried for :people

tony.kay18:04:32

@ethangracer: react-key is a cheat for forced refresh or entire UI...causes React itself to ignore shouldComponentUpdate

vmarcinko18:04:18

ok, thanx, i exactly wanted to ask that very question of the hting I just mutated appears in multiple places in UI tree, and me as one components doesn't want to know where are those, and computed callback seems a solution for only when one parent component is re-rendered, and there can be many components that need to re-render

tony.kay18:04:41

right: follow-on reads is how you do that

tony.kay18:04:10

callback only makes sense when you're trying to make the kind of isolation things like button want

tony.kay18:04:21

kind of a direct use....date picker, etc

vmarcinko18:04:41

thanx @tony.kay , totally clear answer simple_smile

tony.kay18:04:34

as a side note, it is also an additional reason to namespace your query keywords, so you don't accidentally over-render unrelated things

vmarcinko18:04:22

btw, do you always first try with callback, and use follow-on-reads only when there are bunch of components to re-render?

vmarcinko18:04:31

or opposite?

tony.kay18:04:57

nope, use-case specific, according to what I said above: callbacks when you have direct parent-child relationship where callbacks make sense... e.g. :datePicked, :listItemAdded, etc. = callback Use follow-on reads when the things are more loosely coupled...e.g. added a friend, and the friend count on the other side of the UI should update.

tony.kay18:04:49

adding a friend probably uses both simple_smile The parent (assuming it is listing friends) sends in a callback for :friendAdded into the UI for adding a friend, and the transact! coded in the parent does a follow-on read for :friend-count

tony.kay18:04:56

if "Add Friend" is a button, you'd use :onClick...same pattern

vmarcinko18:04:31

btw, I'm totoal UI/react noob, but when you menitoned that other frameworks with reactive way, although figure out themselves what UI portions must change can re-render huge trees - I thought that react is good exactyl for this purpose - huge virtual DOM is re-rendered, but real DOM is not, so its fast ,no?

vmarcinko18:04:16

but as you say - performance is hurt then

tony.kay18:04:17

that is the general promise of react: If you ask it to re-render something it only makes the DOM changes needed. However, VDOM is cheap, not free

tony.kay18:04:58

skipping the vdom diff improves it even more

vmarcinko18:04:16

ok, so its not so fast

vmarcinko18:04:26

to be negligible

tony.kay18:04:31

It is orders of magnitude faster than real DOM manipulations

tony.kay18:04:40

but yes, not negligible

tony.kay18:04:30

that is why React has a shouldComponentUpdate method...try to short-circuit whole parts of VDOM diff for the UI tree

vmarcinko18:04:53

And one more question - whole one app state atom thing is great, and one reason is that you can do history walk, but is there any practical limit to how long can such app be alive in one's browser, because we're basically keep consuming more and more space

tony.kay18:04:16

you're not consuming more and more space. This history is limited to 100 steps by default.

tony.kay18:04:25

and each delta does structural sharing

vmarcinko18:04:26

aha, didn't know that

tony.kay18:04:41

so the steps are cheap, even for large app state

vmarcinko18:04:55

yeah, i know about persistance structures, but default maps/vectors don't have history limit as I know, so I assumed here the same

tony.kay18:04:27

??? default maps/vectors and history ???

vmarcinko18:04:47

default maps/vectors and other persistent data structures in clojure

vmarcinko18:04:10

when you mutate them, other snapshots of them are kept if someone has references to them

vmarcinko18:04:15

so they grow over time, no?

mahinshaw18:04:31

only as long as there is a reference

tony.kay18:04:34

so if you keep reference to things, yes, they consume memory

tony.kay18:04:58

but "history" is something implemented by the app...in this case by Om. It keeps 100 refs to 100 steps of app state

vmarcinko19:04:09

aha, ok, got confused

tony.kay19:04:39

and then of course each step is sharing most of the last

vmarcinko19:04:46

can this histroy limit of 100 be raised

tony.kay19:04:47

There is a GC issue for things in your state...and you are responsible for that. Say you load 100 items, and then let the user page...say through 10000000 results. If you gather those up for each page they click and never clean any out, well, you're going to have problems. Nothing Untangled/Om can do for you.

tony.kay19:04:01

Yes, the history limit can be set...not a hook for it yet, though

vmarcinko19:04:19

it seems it will get spent very fast if you use set-string! with onChange handler for some text field

vmarcinko19:04:33

as I've seen on your clojrue west presentation

tony.kay19:04:54

Also, the current serialization of the app state over the wire doesn't compress to take advantege of s.sharing. So at the moment a support request is way more expensive than it need be

vmarcinko19:04:59

basically, one mutation for each char entered

tony.kay19:04:09

Ah, on the input fields

tony.kay19:04:28

That is one place where I think using component local state can be ok

tony.kay19:04:44

not wanting to record each keystroke in an input text area or field

tony.kay19:04:24

for exactly that reason

cjmurphy22:04:37

@vmarcinko: I've found that it is sometimes a good idea for your follow on read 'keys' to be idents. So if a particular instance of a component needs to be updated give its ident as the follow on read. Most (actually all in my case) of the keys are at the root level, so specifying one of them as the follow on read just ensures that everything gets re-rendered, which is not what you would usually want to happen.

tony.kay22:04:46

@currentoor: much better implementation of parallel loading coming. I'm going to merge it even. Much simpler.

currentoor22:04:20

@tony.kay: awesome! looking forward to it

ethangracer23:04:39

@cjmurphy: what’s the syntax you use for that?

tony.kay23:04:42

@currentoor: It's up. new jars on clojars for snapshot of client (server 0.4.7 is fine...didn't need to modify the server at all). The cookbook is updated with the recipe...and it is trivial to use....just add :parallel true on any load-field or load-data

tony.kay23:04:21

bypasses the sequential queue

tony.kay23:04:37

@ethangracer: that is what you're needing for reports, too

ethangracer23:04:57

@tony.kay: awesome, I’ll take a look at that soon as we go client-side

cjmurphy23:04:09

@ethangracer: It really is just the ident instead of the key. I'll give an example from my source.

cjmurphy23:04:27

(om/transact! cell `[(graph/remove-line {:graph-ident [:trending-graph/by-id 10300] :intersect-id ~id}) [:trending-graph/by-id 10300]])

ethangracer23:04:08

huh, have you confirmed that it specifically targets the ident? I’m surprised because that isn’t part of om’s query syntax to my knowledge

cjmurphy23:04:11

I just put the numbers in directly because they don't change 😝

tony.kay23:04:18

Ident-based refreshes do tend to tie you to a specific UI...which can be ok

tony.kay23:04:29

@ethangracer: those are not queries...they are follow-on reads

tony.kay23:04:39

the query is generated by Om behind the scenes

ethangracer23:04:03

I thought that follow-on reads weren’t ‘special’ so to speak

tony.kay23:04:13

if you give just a keyword, it looks up ALL components that query that keyword, finds the queries that have to run to update them, etc.

ethangracer23:04:15

that it just did a read of those keys by finding their components in the indexer

tony.kay23:04:35

same with ident

ethangracer23:04:36

so idents do work as follow-on reads, that is good to know @paul4nandez

tony.kay23:04:44

follow on reads use indexer

tony.kay23:04:43

but in general I would recommend the more abstract thing and do follow-on reads on keywords...idents can suffer from under-refreshing, since you might later extend your UI to use dependent data that is queried with similar keywords.

tony.kay23:04:19

obviously they are supported for a reason...just noting that you're tying your refresh to a particular use of data, as opposed to the data itself

tony.kay23:04:34

sort-of 😉

cjmurphy23:04:00

I never understood using keywords as follow on reads - because they are all at the root and it seems everything gets re-rendered.

tony.kay23:04:11

they are NOT all at the root

tony.kay23:04:28

when you do a follow-on with :xyz, it looks in ALL on-screen component queries for :xyz

tony.kay23:04:04

:person/name will re-render all components that ask for a person's name

tony.kay23:04:12

it is much more general and abstract

cjmurphy23:04:14

But :xyz is part of the root query - so only top level keys of a component.

tony.kay23:04:19

can be anywhere

cjmurphy23:04:52

I meant re-rendering done by examining top level query keys of a component.

tony.kay23:04:11

sure, but ident is the same there...you're going to re-render the sub-tree of whatever you target

cjmurphy23:04:17

Must be otherwise would get them all.

tony.kay23:04:18

but that is not Root (top level root)

tony.kay23:04:52

you always get a sub-tree re-render, because there is no telling how the data that changed might be used (or derived) further down

tony.kay23:04:06

but the sub-tree can start from any component

cjmurphy23:04:41

The keys I always thought of targeting must have been in top level of root - which no good - then I found using ident very good at specific targetting.

tony.kay23:04:49

nope...it does what I said above. keyword -> indexer -> components that query for keyword -> re-render

tony.kay23:04:35

it is an abstraction that lets you think about your data, instead of your UI. "Run this mutation, then re-render anything that asks for this kind of data"

cjmurphy23:04:09

In the above example I have a checkbox in a grid. One of many. If you check it then you want the graph to have that extra line in it. So you want the graph to re-render - thats what the ident as follow on does. Also I pass the cell to the parent. The transact happens to be done in the parent, but only the particular cell is re-rendered. So I achieved what I wanted.

cjmurphy23:04:04

cell being a checkbox component.

tony.kay23:04:04

Sure. I'm not saying idents don't work...just that you misunderstood follow-on reads

tony.kay23:04:44

you could have something that has a different ident (e.g. a table) that queries for the same data. Doing a follow-on read for the query would re-render both....an ident would only re-render one

cjmurphy23:04:13

Yes sure, I get that.

tony.kay23:04:18

unless you gave the table and graph the same ident, which would then mix the graph-local and table-local details

cjmurphy23:04:35

key is like class, ident is like object.

tony.kay23:04:57

k. I'm outa here