fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
tvaughan 2021-01-21T12:33:36.005100Z

From https://book.fulcrologic.com/#_initial_route > The function `dr/ssr-initial-state` (written, but untested) can be used to help you construct the proper state for a given path (which must be used for the server-side render, and also as the initial state for the client). However, I don't see this function in a github search. What's the correct way to set the route when using server-side rendering? Naively I tried:

initial-state (ssr/build-initial-state sessions-data ui/Root)
                  script-tag (ssr/initial-state->script-tag initial-state)
                  props (denorm/db->tree (get-query ui/Root) initial-state initial-state)
                  content (do
                            (dr/change-route! app path)
                            (binding [*app* app]
                              (dom/render-to-str (ui/ui-root props))))
Which results in nil passed to get-query when dr/change-route! is called

Jakub Holý 2021-01-22T08:20:09.014900Z

BTW does this work in the browser and only is broken on the backend or is it a problem in both? So that we can zoom in on the problematic area...

Jakub Holý 2021-01-22T08:28:47.015100Z

After re-reading your original message, it seems there is indeed a problem with the initial state. I would try to debug it on the JVM to find out what change-route is failing to find. Namely it seems (dr/evaluate-relative-path Root ["main"]) is not finding the router. Look at why. Could you perhaps make a small replication case (e.g. based off fulcro-template) so that we could play with the code and try to help better? Hm, looking at the code it seems there is simply missing support for this...

tvaughan 2021-01-22T10:30:52.015800Z

> Hm, looking at the code it seems there is simply missing support for this... Bummer. would you like me to still make the small replica?

Jakub Holý 2021-01-22T11:32:14.016Z

No, I have made one. Check out this https://gist.github.com/holyjak/6ead10c0b447e098026f3e24e4f1e519 It works and displays the non-default route. However I have no idea what is the correct way to do this. This is simply the first one I found to work.

tvaughan 2021-01-22T11:48:25.016200Z

Cool. So sticking with this approach for now, where you use AlternateTarget to compute state' the backend should really look at the requested uri and pick the appropriate component?

👍 1
Jakub Holý 2021-01-22T12:28:50.016500Z

RAD's history API has utilities for converting URLs to routes and the dr ns has utilities for finding the target given a route so look at those. Maybe they will work 🙂

tvaughan 2021-01-22T12:30:05.016700Z

Cool. Thanks! will do

tvaughan 2021-01-22T18:28:03.017300Z

Thanks a lot @holyjak. This is mostly working for me. I need to insert the initial state of a Session component. This:

(let [session-state (parsers/sessions-endpoint [{:sessions/session (get-query sessions/Session)}]
                                                 {:target [:component/id :session]
                                                  :remote :sessions
                                                  :request request})

        initial-state (-> session-state ;; TODO: This is not how session-state should be added
                          (assoc-in
                            (conj [::dr/id ::ui/SiteRouter] ::dr/current-route) (get-initial-state target))
                          (set-query*
                            ui/SiteRouter {:query [::dr/id {::dr/current-route (get-query target)}]}))

        script-tag (ssr/initial-state->script-tag initial-state)

        content (let [app (fulcro-app/fulcro-app {:initial-db initial-state})
                      props (denorm/db->tree (get-query ui/Root) initial-state initial-state)]
                  (binding [*app* app]
                    (dom/render-to-str (ui/ui-root props))))]
    {:script-tag script-tag
     :content content})
isn't working. session-state is correct, but it's not inserted into the db correctly.

tvaughan 2021-01-22T18:28:45.017600Z

Any hints on how this should be added? Thanks again!

tvaughan 2021-01-22T18:57:59.019200Z

Looking at this https://book.fulcrologic.com/#_building_the_app_state_on_the_server now ...

tvaughan 2021-01-22T19:52:10.019900Z

Yeah, this seems to be what I needed. Now I seem to have a problem with the state machine ...

Jakub Holý 2021-01-22T20:30:13.020800Z

I don't follow, sorry. If you know what the session state should be then just insert it into the state at the correct position (assoc-in state [:comopent/id :session] your-stuff), no? And why do you need state machines in a non-interactive app?

Jakub Holý 2021-01-22T20:32:25.021Z

You could render whatever you want on the client side then dump the state into an EDN file, remove everything unnecessary, and use that for SSR

tvaughan 2021-01-23T00:29:26.021200Z

> then just insert it into the state at the correct position (assoc-in state [:comopent/id :session] your-stuff), no? Correct. The documentation I linked to said to do exactly this. Thank you. What I have now is:

(let [session-state (parsers/sessions-endpoint [{:sessions/session (get-query sessions/Session)}]
                                                 {:target [:component/id :session]
                                                  :remote :sessions
                                                  :request request})

        session-ident (get-ident sessions/Session session-state)

        initial-state (-> {}
                        (assoc :sessions/session session-ident)
                        (assoc-in session-ident session-state)
                        (assoc-in (conj [::dr/id ::ui/SiteRouter] ::dr/current-route)
                          (get-initial-state target))
                        (set-query*
                          ui/SiteRouter {:query [::dr/id {::dr/current-route (get-query target)}]}))

        script-tag (ssr/initial-state->script-tag initial-state)

        content (let [app (application/fulcro-app {:initial-db initial-state})
                      props (denorm/db->tree (get-query ui/Root) initial-state initial-state)]
                  (binding [*app* app]
                    (dom/render-to-str (ui/ui-root props))))]
    {:script-tag script-tag
     :content content})
The app is very much an interactive app. The server now includes the session data, and renders the correct content (the content associated with the requested route). The client however has a state machine that checks that the session data is valid, and if not will run through its state machine to sign the user in. What I assume I need to do is trigger the state machine on the server-side so that the client will be in the "session data has been checked and is valid" state when rendered in the browser. I think what's happening now is that the client is needlessly checking the validity of the session data which is causing some unnecessary page flickering

tvaughan 2021-01-25T14:57:26.048900Z

Nope. The problem was with a resolver that wasn't returning the data in the right shape.

👍 1
Jakub Holý 2021-01-21T15:41:35.005300Z

You do SSR in Java? Do you not use any JS libraries in your UI?

tvaughan 2021-01-21T15:51:16.005800Z

> You do SSR in Java? Yes

tvaughan 2021-01-21T15:51:58.006Z

> Do you not use any JS libraries in your UI? Yes, and anything that's JS only is stubbed out for Java

😻 1
😱 1
tvaughan 2021-01-21T15:54:21.006200Z

SSR is working, except I don't know how to set the route on the global app before rendering it to a string which means the default route is always used

tvaughan 2021-01-21T15:58:02.006400Z

I see you're doing (dr/change-route! app ["main"]). Perhaps I formatted the url incorrectly? ... I'll look more closely

Jakub Holý 2021-01-21T19:00:42.010100Z

Hi! I have started working on a "minimalistic" Fulcro tutorial. The first, still quite raw, draft is here: https://github.com/fulcro-community/guides/blob/minimalist-fulcro-tutorial/minimalist-fulcro-tutorial/index.adoc However, early feedback is welcomed. (Beware - it refers to yet not existing EQL tutorial. Don't be confused.)

❤️ 7
Jakub Holý 2021-01-22T08:18:43.014700Z

That is great to hear, thank you so much! It provides fuel to go on 🙂

2021-01-22T09:02:41.015400Z

Really nice and thank you!

Jakub Holý 2021-01-22T10:13:06.015600Z

BTW if you find anything unclear, any mistakes, or have any suggestions, do not hesitate to let me know!

2021-01-22T20:07:30.020300Z

If you're going to give a substantial list of pre-requisites (react/graphql alone are potentially months of learning!) - linking to a tutorial/guide or two would be a good idea.

👍 1
tony.kay 2021-01-23T01:07:10.021400Z

@holyjak I’m adding public comments via http://hypothes.is

❤️ 1
Jakub Holý 2021-01-23T13:38:13.024100Z

Thanks a lot, Tony! I've incorporated them now.

dgb23 2021-01-21T20:04:26.010300Z

pictures! Big fan! I’m still working through the full book but I’m taking notes as was suggested. Ill post them here in a week or two (have too many things going on atm). Maybe it’ll be useful.

💯 1
tvaughan 2021-01-21T20:05:16.010500Z

The exception goes away when I add (each of these seem to be required):

(set-root! app ui/Root {:initialize-state? true})
    (dr/initialize! app)
    (dr/change-route! app uri)
    (mount! app ui/Root "app")
although the content that's rendered server-side is still of the default route (no matter where they're inserted in the chain above). I'm going to table this for now as there are other items higher on our priority list. Thanks for taking the time to respond @holyjak, and helping me get further along

Jakub Holý 2021-01-21T20:12:57.011Z

Is your change route correct? Is your initial state correct?

tvaughan 2021-01-21T20:29:30.011200Z

The change route is correct, I think. The UI does think it has been routed to the uri passed to change-route! The initial state most likely isn't correct. I only query for a Session component. I assumed the server-side rendered content would match the set route, even without initial data, e.g. requesting /teams should show the Team header but the list of teams would be empty. Instead the UI thinks it has been routed to /teams but the dashboard, the default route, is shown instead.

Jakub Holý 2021-01-21T20:38:11.011400Z

> the UI thinks it has been routed to /teams but the dashboard, the default route, is shown instead I have experienced something similar (actually the opposite). I suppose that "UI thinks it has been routed" means that the state of the router is as you expect, correct? I guess there must be a broken link somewhere, perhaps the router parent does not have the correct initial state.

tvaughan 2021-01-21T20:41:02.011600Z

> the state of the router is as you expect, correct? Correct. The router looks like what I expect

tvaughan 2021-01-21T20:44:26.011800Z

> perhaps the router parent does not have the correct initial state. I don't know. I'm assuming that if I have the correct initial state, I can use it with the root component to render the right content provided I've routed the app correctly. Or do I need to render the content using the component associated with the route?

tvaughan 2021-01-21T20:47:14.012Z

In my initial snippet above, I use ui/Root and the ui-root factory no matter what the route is

Schpaa 2021-01-21T23:42:59.014100Z

This is awesome and very welcome. I’m new to fulcro and you’ve answered so many questions I’ve had with your writing. Thank you!