Fork me on GitHub
#fulcro
<
2020-03-17
>
tony.kay15:03:46

@tvaughan The result action for loads is what does the merge and targeting. My guess is that your target is targeting through a to-many, and you’re therefore using a keyword as a key into a vector

tvaughan15:03:21

Thanks for the reply @U0CKQ19AQ. There is a to-many relationship to a list of team names which does work. The selected team is one-to-one. I definitely do doubt that my query is correct. I have team-list/selected-team in the query so that it'll compile but I'm really not sure what the correct shape should be as this is never meant to actually be sent over the wire. It's just a reference in the client database. It definitely seems odd that selected-team disappears from the client database (as seen in Fulcro Inspect) after the list of team names is fetched

tony.kay15:03:56

So, looking more closely at your TeamList code. Your query needs to explicitly join the selected team to the UI of the selected team: [{:team/selected-team (get-query Team)}] You’re referencing a normalized entity (by name, I assume, from your load). I’m also confused why in one place you’d refer to it as :list/by-attr and in another as :temp/name. Don’t resolve the same data using different things: An entity/row/document in your real database should have a real ID, and THAT is what you should normalize by, otherwise you’re going to confuse the heck out of yourself. One of the main points of Fulcro is to normalize your data model so things stay sane. If you suddenly normnalize based on a bunch of different names/keys/ids for the same thing, you’ve just propagated the same error that nearly every other UI library on the planet seems to want you to make.

tvaughan16:03:28

> [{:team/selected-team (get-query Team)}] I had this originally. I just tried it again and same error. > I’m also confused why in one place you’d refer to it as :list/by-attr We're using Datomic on the backend. :list/by-attr is just a query that will return all entities with a certain attribute.

tony.kay16:03:19

you should never target an ident

tony.kay16:03:16

an ident is already a location in a database…you’re loading one thing and then rewriting an IDENT into a table instead of a map. Targeting should target either: a top-level key, or an attribute in a table entry.

tony.kay16:03:26

table entries are always meant to be maps

tony.kay16:03:45

but loading by ident, and then targeting to where a table entry should be will wreak all sorts of havoc

tony.kay16:03:27

The UI reified graph needs to follow the very simple model described in the book: {TABLE {ID {map of attributes}}}

tony.kay16:03:43

where an attribute in that map may have an ident value to create an edge

tony.kay16:03:56

load! normalizes data. :target adds edges

tony.kay16:03:27

the only place edges are legal is at the root level of the entire db (since it can hold db table and edges), and as attribute values.

tony.kay16:03:51

so your target causes {TABLE {ID [TABLE ID]}} which is not legal

tony.kay16:03:15

a table’s entry cannot be an ident. it must be a map

tony.kay16:03:45

I should add an error check for that case for beginners. I can see how you could make the mistake or misunderstand it.

🙏 4
tony.kay16:03:37

See book/videos on properly loading/working with lists.

tvaughan16:03:57

OK. I think I understand now. Thanks for taking the time to spell this out to me

tvaughan16:03:16

> See book/videos on properly loading/working with lists. I did. I thought I did this correctly 😞

tony.kay16:03:57

well, then my bad for not being clear enough 😛

tvaughan16:03:18

Just to be clear, the list of team names does render correctly. It's the onClick elsewhere that chokes

tony.kay16:03:57

The error you’re getting is because the target you specified is trying, after the component did mount, to target what should be an attribute in the proper model, but what it finds instead of a vector instead of a map.

tony.kay16:03:17

your data model is wrong. period. Things will break.

tvaughan16:03:05

> your data model is wrong. period. Things will break. 👍 Thanks

tony.kay16:03:31

welcome. What you want is to give the data that you’re loading a proper normalization story. Invent a resolver for the server for a top-level key (i.e. :all-teams). (load! :all-teams Team {:target [:list/id some-list-id :list/all-teams]})

tony.kay16:03:02

the list has an ident (fixed in your case)

tony.kay16:03:58

your onClick load is right

tony.kay16:03:07

is your mount that is messed up

tvaughan16:03:36

> your onClick load is right That's good news 😉

tony.kay16:03:14

it should be something more like:

(load! this :team-names {:target (conj (get-ident this) :by-attr/resp)})

tony.kay16:03:31

where :team-names is just some global resolver that can give you the answer you want

tvaughan16:03:47

Global resolver on the client-side only?

tony.kay16:03:11

use :params to send parameters if you need them. Loading by ident is for loading an entity in it’s entirety, and would never have an ident target

tony.kay16:03:23

no no…global resolver is a pathom resolver with no input

tvaughan16:03:33

Cool. That's what I thought

tony.kay16:03:35

only output of some (usu. namespaced) keyword that has meaning

tvaughan16:03:06

This all very helpful. I think I know what to do now

tony.kay16:03:18

you’re normalizing team name by :db/id

tony.kay16:03:23

which I do not recommend

tony.kay16:03:31

use a namespaced keyword for that that describes the domain (i.e. :team/id). It will save you lots of headaches later.

tvaughan16:03:52

Right. I saw that mentioned somewhere. I'll update that too

tony.kay16:03:56

it’s OK that it is in the database as :db/id…just don’t call it :db/id.

tony.kay16:03:47

you want all info for the team entity going in the same spot, and you’d like to be able to debug it that way as well. As your app state gets complex, having one giant table of heterogenous entities will be info overload

tvaughan16:03:09

I guess I was thinking I would like to avoid having team/id, team-name/id, selected-team/id, etc when really they're all just db/id

tvaughan16:03:21

*The same db/id

tony.kay16:03:23

every component should use :team/id as ident.

tony.kay16:03:54

:team-list/selected-team is an attribute that, in the UI model, should end up pointing to a [:team/id id]

tony.kay16:03:22

team name is an attribute at that ident in the db

tony.kay16:03:31

you only want it in one place per team ever

tony.kay16:03:42

otherwise you lose all benefits of normalization

tvaughan16:03:00

That I understand. I thought that's what I was doing

tony.kay16:03:57

Why is TeamName a component?…it’s really just TeamListRow, right?

tony.kay16:03:13

but each row represents a team

tony.kay16:03:59

and that Row is just one possible view…so, yes, looking over your code in general it does seem like you’ve generally got it, other than you’re using :db/id for every kind of thing, which then makes pathom resolvers hard to write correctly

tony.kay16:03:23

everything has an input of :db/id…so your autocomplete in the query tab will say everything is accessible from :db/id

tony.kay16:03:48

and you’ll have no reasonable chance at a security model based on names, because your ids are all collapsed into one concept (name)

tvaughan16:03:23

True. Originally I loaded everything in TeamList, but I want to be able to load the most recent team data from the backend each time a team name is selected which led me to where I am now

tony.kay16:03:35

but sorry..you really didn’t ask for any of that commentary 🙂

tony.kay16:03:59

(defsc TeamList [this {:team-list/keys [teams]}]
  {:ident (fn [] [:team-list :singleton])
   :query [:ui/selected-team {:team-list/teams (get-query TeamName)}]
   :componentDidMount (fn [this]
                        (load! this :all-teams TeamName {:target (conj (get-ident this) :team-list/teams})}

  (map ui-team-name teams))
  

tvaughan16:03:21

All good! :male-student: Class is in session 😉

👍 4
tvaughan16:03:19

> because your ids are all collapsed into one concept (name) In this example names have a unique constraint

tony.kay16:03:05

no, I’m saying that your person entities are in the same fulcro db table as your team entities

tvaughan16:03:25

> ;; you'd probably use computed state to pass in a selectTeam callback, and at this layer you'd make that be a fn that does a m/set-value! of :ui/selected-team. Yeah, that's what I couldn't figure out how to do

tony.kay16:03:40

you could do it that way, or in your case you wanted to load something

tony.kay16:03:43

the team details

tony.kay16:03:01

and we already discussed how to do that

tony.kay16:03:09

it will work if you fix the list

tvaughan16:03:22

Cool. Thanks!

tvaughan20:03:27

Thanks again for your help @U0CKQ19AQ. If you're curious, this https://gist.github.com/carrete/6e77d0da5e12a6b0e6d600ac7972fb1a is what I came up with

tony.kay21:03:28

yep, that looks better

tony.kay15:03:22

it’s also possible that your response “looks right” on casual observation, but actually has the wrong shape causing merge to fail for similar reasons

tvaughan15:03:08

I had it directly inline in a component and it did work then just to verify