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 calledBTW 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...
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...
> 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?
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.
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?
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 🙂
Cool. Thanks! will do
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.Any hints on how this should be added? Thanks again!
Looking at this https://book.fulcrologic.com/#_building_the_app_state_on_the_server now ...
Yeah, this seems to be what I needed. Now I seem to have a problem with the state machine ...
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?
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
> 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 flickeringNope. The problem was with a resolver that wasn't returning the data in the right shape.
You do SSR in Java? Do you not use any JS libraries in your UI?
https://github.com/dvingo/dv.fulcro-template/blob/master/resources/clj/new/dv.fulcro_template/src/main/app/node_server.cljs uses nodes for SSR
> You do SSR in Java? Yes
> Do you not use any JS libraries in your UI? Yes, and anything that's JS only is stubbed out for Java
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
I see you're doing (dr/change-route! app ["main"])
. Perhaps I formatted the url incorrectly? ... I'll look more closely
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.)
That is great to hear, thank you so much! It provides fuel to go on 🙂
Really nice and thank you!
BTW if you find anything unclear, any mistakes, or have any suggestions, do not hesitate to let me know!
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.
@holyjak I’m adding public comments via http://hypothes.is
Thanks a lot, Tony! I've incorporated them now.
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.
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 alongIs your change route correct? Is your initial state correct?
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.
> 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.
> the state of the router is as you expect, correct? Correct. The router looks like what I expect
> 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?
In my initial snippet above, I use ui/Root and the ui-root factory no matter what the route is
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!