Fork me on GitHub
#fulcro
<
2019-04-04
>
Andrew00:04:23

I'm trying to wrap my head around Fulcro and I'm a bit confused about the queries composing to root. From what I can understand from the docs you have to construct a tree of queries that always leads back to your root component. Doesn't that eliminate (a lot of) the benefit of a global client side database by requiring you to construct a rigid hierarchy of data that must be refactored all the way up to root if you want to change anything? I think I am probably misunderstanding the concept.

currentoor04:04:31

Hi @UHDB725UG, I may be misunderstanding you but no, moving components around is a cinch

currentoor04:04:46

the (new) parent just needs to compose the child component’s query and :initial-state (which has a map notation shorthand)

currentoor04:04:15

since the client database is normalized, the rest will just work

currentoor04:04:54

is there a particular use-case you are worried about?

Andrew09:04:18

Sorry but I'm still a bit confused. Let's say I have the following component tree: Root -> A -> B -> C -> D I want to add component E which has the query [:foo] as a child of D. Doesn't this mean that I must now modify D, C, B, A, and Root to also query for [:foo]?

wilkerlucio10:04:29

@UHDB725UG no, in the example you mentioned you would only change query of D

Andrew10:04:52

Hmm, I still don't quite get it. By modifying D aren't I also required to modify C? That's what the docs make it sound like here http://book.fulcrologic.com/#_feeding_the_data_tree "In this way an arbitrary component in the UI tree is not querying for its data directly in a side-band sort of way, but is instead being composed in from parent to parent all the way to the root." In my example if D does not already have the data for :foo from existing props then doesn't it need to be passed in from C and so on up to root?

wilkerlucio10:04:06

no because components read queries from each other, so in the Root you will have {:link-to-a (fp/get-query A)}, on A you will have {:some/link-to-b (fp/get-query B)}... and that goes on, the deep children of A is unknown to the root, each component only knows about its direct children, so if something indirect changes the componet doesn't have to care about it, makes sense?

👍 4
Andrew15:04:23

Ah that makes much more sense now. Thanks!

Andrew16:04:21

Does that mean that you don't always have props names with semantic meaning? For example if A is a "TodoList" naming its props "todos" won't make sense because its props will be more like "todos-and-foo-and-whatever-else-children-need".

wilkerlucio16:04:17

yeah, I think it helps if you really separate the things out, on the things we are discussing there are 3 things to have separated: 1. component names, this is just the component name, and for our purposes here it always have an associated query as well 2. there are "value properties", labels that point to final values (like :todo/title) 3. there are "link properties", properties that actually point to other entities, this can be to-one or to-many, eg: :user/all-todos, :user/last-interacted-todo so usually the parent will use 3 to point to something, while the component itself will declare what it needs from that resource, and the chain keeps going until you finish representing your entire application.

wilkerlucio16:04:01

another interesting thing to know, in Fulcro when you use fp/get-query, that's not the same as just getting the query value, fp/get-query vector will also have meta-data pointing to the original component (you can check it by running: (meta (fp/get-query SomeComponent))), this is important to know because Fulcro uses this information to normalise the data, so keep in mind that the full root query will have the whole page requirement, but also pointers that can tell what component asked for what part

currentoor17:04:40

just one thing to add, todos-and-foo-and-whatever-else-children-need you should not name props like that, that’s how you break encapsulation

currentoor17:04:00

for child component called TodoList, name it’s factory function ui-todo-list and it’s query key {:todo-list (prim/get-query TodoList)}

currentoor17:04:04

the parent does not need to worry about what subcomponents or top level links TodoList (or it’s children) need, parent is only concerned with delegating to it’s direct child

currentoor17:04:53

that’s the whole point of co-located queries, data requirements are specified only where the data gets used

Andrew20:04:47

Thanks for all the help and clarifications. Things make a lot more sense now. I just need to spend some more time playing with Fulcro to digest all the concepts from those epic docs haha.

currentoor20:04:43

yeah that book’s more of a reference manual than a tutorial 😅

levitanong08:04:38

Does dynamic routing support a route target whose segment is nil or ""? (to simulate viewing a directory, for instance) Or is it incubator’s opinion that routing should only be done to leaves? (Edited for clarity)

tony.kay15:04:02

Have not tested it, and have no opinion. You make the array of segments, and return things that can match them from the targets. D.R. is not considered a “compete product” and is certainly open to suggestions/improvements…that’s why it is incubator

levitanong10:04:50

Awesome. I’ve tried "" and it works as I suspected/wanted.

levitanong10:04:27

Is there a version of current-route that is compatible with SSR?

tony.kay16:04:45

you’d have to test/write…I’ve not done any SSR verifications against incubator

tony.kay16:04:01

the ns isn’t that big

beetleman11:04:44

hello! I need some help:/. I have this query (this i example sa i know i can model data in this way that I will not have this problem, but I want avoid this)

:query         [:user/name
                   :user/id
                   {[:account/by-user-id 1] ; how can I use `:user/id` from previous line here
                                        ; can i use some sort of placeholder for it?
                    (prim/get-query Account)}]
  
it work using this static id 1

tony.kay15:04:06

@mateusz.probachta you cannot, as that would require a multipass query processor or some other kind of logic in the query engine that isn’t there

tony.kay15:04:24

why do you have two tables for the same id data, why not merge them and use the same ident?

tony.kay15:04:03

that’s why you have namespaced keywords…if there really is a to-one situation like this, then you can safely merge the two under a single ident and have any number of components link to the same map in the database

beetleman15:04:35

It was oversimplefied example. But yeah, i can handle this this by merge or other data transformation during loading data.

mss18:04:54

anyone taken a crack at building a drag n drop interface w/ fulcro? I’m trying to choose between wrapping a react dnd library versus rolling my own. any insights or tips would be appreciated!

currentoor19:04:04

i’ve built my own from scratch in the past, it’s possible but a lot of work to make it polished and feature complete

currentoor19:04:32

workspaces is an open source fulcro app, which uses react-grid-layout for drag/drop/resizing

currentoor19:04:27

you can checkout it’s source and see what’s involved, that’s probably the approach i’d take, if possible

👍 4
currentoor19:04:07

also it’s a good example app of how to do fulcro the right way, it’s author @U066U8JQJ knows his stuff!

Andrew20:04:05

I'm new to Fulcro but I have a lot of experience with various React drag and drop libraries. If your use case is simple "react-beautiful-dnd" is probably your best bet. If you need more flexibility "react-dnd" is fantastic but significantly more complex.

Andrew20:04:55

DnD that works well across all desktop and mobile browsers is a pretty tricky problem especially if you want stuff like nested drop targets and auto-scrolling. React-dnd was the only library I found that could pull off everything I needed in a JS project.

👍 12
mss20:04:51

thanks for the feedback y’all – really appreciate it. I think for ease of interop I’m going to take a crack at integrating react-beautiful-dnd which is on cljsjs and seems to have a more straightforward api

currentoor21:04:27

@U04V5HFPU FYI if you’re using shadow-cljs you don’t even need to bother with cljsjs

currentoor21:04:37

you can use javascript packages directly

jyriand19:04:22

hi. I’m trying to figure out how to set up my development environment using emacs any cider. Do you know of any guides or have some tips?

currentoor19:04:55

if you use lein to generate the project base, it includes a readme with instructions for starting the server and client from the command line

currentoor19:04:44

both run nrepl servers (if the server doesn’t by default it can be configured, i know shadow-cljs does)

currentoor19:04:59

then you can have cider connect to those nrepl servers

currentoor19:04:40

the usual cider setup, which means you need to add the cider/piggieback middleware to your app

currentoor20:04:01

i use intelliJ now but used to use emacs with fulcro all the time

adamfeldman00:04:57

As someone new to Emacs, Spacemacs has enabled me to get started http://spacemacs.org

Andrew21:04:30

Is there a specific strategy for building "offline first" apps with Fulcro? I'd imagine you would apply updates optimistically, store the database and a list of unresolved (no server reply) transactions and then replay and resolve errors whenever network is online. Would a strategy like that work well for offline app support and would it be well supported with Fulcro's architecture?

currentoor21:04:05

it wouldn’t be trivial, but certainly something that fulcro’s architecture makes palatable…

currentoor21:04:38

by default fulcro mutations are optimistic on the client

currentoor21:04:21

all remote interactions are reified, so you can serialize, store, and replay them

currentoor21:04:48

and they are guaranteed to happen sequentially

currentoor21:04:37

the websockets code even has an auto retry option (but then you’ll want to make sure your mutations are idempotent)

currentoor21:04:40

so all the necessary tools are there, but you’ll have to put them together yourself, does that answer your question?

Andrew00:04:55

Yep. Thank you very much!

Andrew20:04:28

Another quick question about this. What info about remote interactions can be stored? If you want to resolve things sometime in the future I think you need to capture user intentions (actions) not just data modifications. There may be conflicts when playing back actions when a user comes online and the semantics of each action/intention would be needed to properly resolve those.

currentoor15:04:19

@UHDB725UG sorry, just saw this now. So that part is left for you to solve, since it’s very application specific. You can have something like a multi-play video game where last edit wins and conflicts don’t matter or a collaborative google docs like editor where conflict resolution can get very complex.

currentoor15:04:48

or the middle case (I’m actually building something like that right now) where you update data only if it has not changed

pvillegas1223:04:40

Trying to set an option as selected with fulcro by passing the value prop to the dom/select component helper. I have print statements around the parent component and dom/select but it is not updating. Weirdly enough, when I hot code refresh a contiguous UI it shows the correct option.

currentoor00:04:06

could be a problem with follow on reads missing?

currentoor00:04:32

hot code refresh forces a re-render from root

currentoor00:04:29

whereas client mutations only re-render starting at the component where the mutation was invoked

pvillegas1201:04:16

The mutation I’m calling is using the parent component as the argument to prim/transact!. I was under the impression that the re-render would be triggered on the parent component (which would trigger part of it’s UI, the dom/select to show the correct option)

currentoor04:04:21

in that case yes, transacting on the parent should trigger a re-render on the child

currentoor04:04:00

i can’t think of anything else, must be something i’m not seeing, do you have a gist?