Fork me on GitHub
#untangled
<
2016-06-10
>
adambrosio00:06:18

@kenbier: feel free to make that than an issue and assign it to me

jasonjckn05:06:20

can I return new state in a remote mutation?

jasonjckn05:06:49

the todomvc has optimistic updates, but let's say I just want to merge in my updates after server processes the remote mutation

jasonjckn05:06:54

non-optimistic updates

jasonjckn05:06:10

the mutation function can return {:action thunk :value ?}

tony.kay15:06:15

@jasonjckn: Remote mutations have no query, so it does not make sense to return a value (because we would not know how to properly merge the result in the graph db)

tony.kay15:06:26

Instead, add in a follow-on remote query

tony.kay15:06:40

Trying to remember where I wrote about that...I know I wrote something about that somewhere

tony.kay15:06:09

It's mentioned somewhat in section H of the tutorial

tony.kay15:06:54

Basically, add an (app/load ...) to your transaction after your mutation. This allows you to specify the query (and optionally the post-mutation symbol) used to integrate the response with your app state.

tony.kay15:06:54

oh wait. I think we fixed the naming of that from app/load...yeah, it is now called untangled/load as of recent versions..

adambrosio17:06:49

@kenbier: in case the avatar pic isnt enough, I’m Pancia on github

therabidbanana17:06:24

@tony.kay: finally got to upgrading to 0.5.0 in our app - noticeably better performance when our data comes back and gets merged in.

jasonjckn18:06:04

tony.kay: thanks! i'll do that

tony.kay19:06:46

OK, fun additions coming to Untangled today! We've worked out a nice way of adding initial state constructors to the UI tree (so you don't have to build initial state in a central single place). Nice and composable, AND it also enables a really cool merge function that is super useful for initializing Union data and merging in incoming components (e.g. from server push).

therabidbanana19:06:46

That sounds pretty useful - especially since we're going to need to figure out server push merging at some point in the not-to-distant future

tony.kay19:06:10

well, it is relatively simple, but I think it cleans up the story nicely.

tony.kay19:06:09

It also helps eliminate the need to ever hand-build initial state (even in the union case, which is a struggle if you're wanting to use unions for your tabs)

tony.kay20:06:29

compose just like queries. No longer need :initial-state when you create the client

tony.kay20:06:48

Rewriting tabbed interface example to show how to initialize union stuff

tony.kay20:06:45

Tabbed interface recipe updated. Shows how to initialize any number of tabs without having to make initial app state as a monolith

tony.kay20:06:56

(on develop, not master yet)

jasonjckn20:06:06

@tony.kay: wicked, i'll put this to use right away

jasonjckn21:06:02

is there a upgrade path documentation from 0.4 to 0.5, i just bumped the dependency and now the app won't start

ethangracer21:06:14

@jasonjckn: what error are you getting?

ethangracer21:06:44

I just upgraded a 10000+ line app and it worked fine, it was just the server refresh that needed some love

tony.kay21:06:51

there was one breaking change, noted in CHANGELOG

tony.kay21:06:04

load-data no longer re-renders root.

jasonjckn21:06:11

i'll dig into it, no worries

tony.kay21:06:48

mount reads app state

tony.kay21:06:55

might be a bug on my end

tony.kay21:06:02

stand by...

tony.kay21:06:31

Yeah, my bad.

tony.kay21:06:39

You're passing an atom as initial state?

tony.kay21:06:49

yep...I forgot to check for that in the new code

tony.kay21:06:08

if you remove initial state and use constructors it'll be fine...I'll fix it, though, of course

tony.kay21:06:14

thanks for the ping

jasonjckn21:06:53

can I use both constructors and initial state in the next version?

tony.kay21:06:03

no, one OR the other

tony.kay21:06:15

but my backwards compatibility isn't right

tony.kay21:06:57

actually, you can sort of use both. If you don't make a root constructor, then you can compose things together by hand and pass them as initial state.

jasonjckn21:06:52

i don't think it would be a problem to move everything to constructors, but i also don't see any design issues with merging constructor state into initial state

tony.kay21:06:30

no way to know how to merge...deep merge? Is it hand normalized? etc etc

jasonjckn21:06:40

hm ok.. in my case it's hand normalized

tony.kay21:06:49

you're better off either doing the app state by hand so it is right, or using just constructors

tony.kay21:06:07

if you need both, and can see a clean way of doing it, then let's talk 🙂

tony.kay21:06:16

but I think you can do it all with constructors unless I've missed something, and it is much cleaner

jasonjckn21:06:53

not all of my links correspond to defui components, for example i load-data into {:search-results {:global {:items ... :total-count ... }}

tony.kay21:06:22

Ah, there is a new function called merge-state! for that

jasonjckn21:06:24

so if I want to initialize :search-results :global with some data, i'm writing hand normalized data into the Root constructor I guess

jasonjckn21:06:38

looking into it

tony.kay21:06:45

well, sort of for that

tony.kay21:06:20

so, instead of putting it in the root constrcutor (in which case you'd have to include the query), you can instead use merge-state! in the started-callback

jasonjckn21:06:23

so that was my use case for passing a hand normalized atom to create client, and also using constructors

tony.kay21:06:36

something like:

tony.kay21:06:58

(merge-state! app ComponentThatHasQueryForNormalize data)

tony.kay21:06:17

that'll normalize it and put it in a table

tony.kay21:06:30

then an extra param lets you join it into app state:

jasonjckn21:06:32

my use case is the data is already normalized and there's no om/Ident that would normalize it

tony.kay21:06:48

You can write defui for non-ui concerns, to normalize data

tony.kay21:06:11

(merge-state! app SearchResult results :replace [:search-results])

tony.kay21:06:39

which is for things like server push as well

jasonjckn21:06:58

so I write defui's for non-ui concerns, then I include them in the Root query (?), even though I don't need this info in the Root props, ?

tony.kay21:06:10

no, you use the query in merge-state!

tony.kay21:06:18

and you run that from started-callback

jasonjckn21:06:35

ok i'll give this a shot

tony.kay21:06:54

Sure. I'm going to try to make a screen cast and put it up on YouTube

jasonjckn21:06:46

what about for links like [:search-results '_] that have hand normalized data like {:search-results {:items .. :total-count ..} this is all supported too?

tony.kay21:06:14

links just point to top-level stuff, see the :replace thing a few msgs ago

tony.kay21:06:31

but you would NOT hand-normalize the data

tony.kay21:06:41

you'd make a simple tree and query to go with it

tony.kay21:06:51

but you already have that tree query somewhere on your UI, I'd guess

tony.kay21:06:13

(merge-state! app SearchResultComponent search-result-tree :replace [:search-results])

tony.kay21:06:34

replace takes a key-path in app state

tony.kay21:06:44

SearchResultComponent must have an ident

tony.kay21:06:30

you could also skip links, and give SearchResultComponent an ident like [:search-results :main], then instead of querying on a link, you'd join on that ident.

tony.kay21:06:35

then you would not need replace

jasonjckn21:06:17

that's :replace [:search-results :main] still?

tony.kay21:06:29

should not need that

tony.kay21:06:42

the merge itself merges the data into the app state at the ident of the component

tony.kay21:06:11

the add-on parameter (replace, append-to, and prepend-to) are for joining the ident into another spot in app state

tony.kay21:06:28

0.5.2 up on clojars. Fixes the bug when passing an atom as app state

tony.kay21:06:51

@jasonjckn: Here is a sample to play with..

tony.kay21:06:11

Look in app.ui at the bottom (comment section)

tony.kay21:06:21

there are several examples of merge-state!

tony.kay21:06:53

use log-app-state to see how app state evolves

tony.kay21:06:33

I just added some comments to help with comprehension...so pull if you already cloned

jasonjckn22:06:50

@tony.kay: i'm testing with 0.5.2 using an atom of initial state, and definitely past that initial exception thank you, however there's some remote data loaded that use to be deeply merged into atom, and now it's replacing that data, here's my initial state:

(def initial-state
  {
   :search-results {:global {:items [] :page-size 1 :offset 0}}

   :search {:tab {:which-tab :search :content "Main"}}
   :settings {:tab {:which-tab :settings :content "Settings"}}

   :current-tab [:search :tab]
   })
as you can see page-size 1, after an untangled/load, the page-size key dissapears

jasonjckn22:06:18

here's the API implementation

jasonjckn22:06:22

(defmethod api-read :search-results
  [{:keys [elasticsearch ref]} key {:keys [query offset] :or {offset 0}}]
  {:pre [(some? query)]}
  {:value
   {:global {:query query
             :offset offset
             :items (->> (esd/search elasticsearch
                                     "entities" "message"
                                     :query {:query_string {:query query}}
                                     :from offset
                                     :size 1)
                         :hits
                         :hits
                         (map :_source)
                         (into []))}}})

jasonjckn22:06:40

obviously page-size is not in there, in 0.4 it use to do a deep merge

tony.kay23:06:32

Does your query to the server include :page-size?

tony.kay23:06:52

if so, Untangled will remove it if the server does not send it...because you asked for it, and it looks like it has disappeared

jasonjckn23:06:13

no it doesn't it has no query

tony.kay23:06:14

your options: don't ask for it (Untangled will leave it), ask for it and reply with it

tony.kay23:06:29

what do you mean it has no query....load-data HAS to have a query

jasonjckn23:06:07

it looks like transact! `[(untangled/load {:query [:search-results]})]

tony.kay23:06:08

in that case the new value will stomp the old

tony.kay23:06:25

it is an opaque value...no reason for automatic deep merge

jasonjckn23:06:26

ok, so if I write the subquery, it'll deep merge?

tony.kay23:06:06

yeah, the server doesn't have to "interpret" the query, so your server code need not change

jasonjckn23:06:18

i'm playing with the Constructor stuff right now

tony.kay23:06:34

good deal. I'm headed home. Good luck!

jasonjckn23:06:04

kk, have a great weekend