Fork me on GitHub
Chris O’Donnell00:05:57

Huh, I don't see a rule about needing to use const anymore. Could've sworn I remembered that being one of the rules, but I guess I'm mistaken. I'll keep hunting for the rule I'm breaking, then. 🙂

Chris O’Donnell01:05:30

In case it helps anyone, I figured it out. I had defined my hook function like so: (def use-styles (styles/makeStyles (clj->js {:root {:width 500}}))). When I tried to bind (use-styles) to classes in a let binding, that compiled into:

var classes = (little_gift_list.ui.root.use_styles.cljs$core$IFn$_invoke$arity$0 ? little_gift_list.ui.root.use_styles.cljs$core$IFn$_invoke$arity$0() :;
I'm guessing that because I didn't define use-styles with defn, the compiler wasn't sure it was a function, and so it added a conditional check for that. The conditional check breaks the rules of hooks. Fortunately, refactoring this to define use-styles with defn fixes it:
var classes = little_gift_list.ui.root.use_styles();

👍 4

@codonnell interesting; what error were you getting with the conditional check?


to elaborate a bit so you get the full context of my question when you pop online: I don’t see why this conditional check would have given you an error. The “rules” of hooks are not strictly enforced rules, they exist because if you wrote your own in ways that infringes them (e.g. wrapped a hook in a conditional), things would break at runtime. The conditional you have here will always evaluate to the same thing at runtime, unless you were to rebind the var to a non-function (which you won’t do). React should be unable to tell that there is a conditional there, and things should just work. You shouldn’t even care there is a conditional there.


The same thing with const. You shouldn’t care about this.

Chris O’Donnell13:05:55


Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See  for tips about how to debug and fix this problem.
And the only difference between getting the error and not was def vs. defn.


:thinking_face: odd


what’s your code with defn @codonnell?


also where is makeStyles coming from? material ui?

Chris O’Donnell13:05:07

Yeah, material ui

Chris O’Donnell13:05:26

(defn use-styles []
  (styles/makeStyles (clj->js {:root {:width 500}})))


Yeah I got the exact same message trying to use their hook API with def


this defn does something different than def though


if you call it in the same way



Chris O’Donnell13:05:33

Ah, you're right.


but still, looking at the material-ui JS code it seems to match what you did with def

Chris O’Donnell13:05:25

No, I think the def was wrong; makeStyles takes a function as its argument that returns a map.


how so? from the JS example it seems to take a JS object


hang on a sec


with the defn version, are you sure it works? As in, not just that it doesn’t throw an error, but are you sure it changes the styles?

Chris O’Donnell13:05:50

No, stopped working yesterday once I got it to compile. Take a look at a component example: It looks to me like it takes a function rather than plain data.


@codonnell I am pretty sure it accepts both. Either a straight up map, or a function taking the theme as argument and returning a map


you are free to chose whether you want to parametarise the style on the theme


wait a sec


@codonnell can you copy/paste (or gist if it’s long) the code you tried to run?


Specifically the component in which you tried to call use-styles


or more directly, answer this question: did you try to call use-styles within a component defined with defsc?

Chris O’Donnell13:05:03

One sec, converting this into a more manageable example (and closing some tabs so my old laptop stops complaining so much).

Chris O’Donnell13:05:21

It is in a function component rather than inside a defsc

Chris O’Donnell13:05:45

(def use-styles #(styles/makeStyles (clj->js {:root {:width 500}})))

(defn my-nav []
  (let [classes (use-styles)]
    (mui/bottom-navigation {:value 0
                            :onChange (fn [event new-value]
                                        (js/console.warn {:event event
                                                          :new-value new-value}))
                            :showLabels true}
                           (mui/bottom-navigation-action {:label "Recents"
                                                          :icon (icon/restore)})
                           (mui/bottom-navigation-action {:label "Favorites"
                                                          :icon (icon/favorite)})
                           (mui/bottom-navigation-action {:label "Nearby"
                                                          :icon (icon/location-on)}))))

Chris O’Donnell13:05:48

The mui/* and icon/* are just wrappers around (apply react/createElement ElementName (clj->js props) children) (or (react/createElement ElementName) for the 0-arity case).


@codonnell how did you include my-nav in the rest of your app?

Chris O’Donnell13:05:29

(defsc Root
  [this {:keys [router header]}]

Chris O’Donnell13:05:11

^ The root component, if it wasn't clear.

Chris O’Donnell13:05:35

The snippet above that I pasted works properly.


@codonnell yeah ok, there lies the issue. You aren’t using my-nav as a “function component”


you are just calling it as a function


from React’s perspective, there is only one component: the Root


and that’s a class component


in which you can’t call hooks


your def version of the hook was correct, the defn was not


the defn didn’t throw because it wasn’t calling the hook, but I would bet it wasn’t changing the styles either


try creating a factory for your my-nav element like so:

Chris O’Donnell13:05:20

Ah, I need to wrap it with react/createElement don't I


(defn ui-my-nav [props]
  (dom/create-element my-nav props))

Chris O’Donnell13:05:14

There it is. 🙂


@eoliphant check out the above thread if you didn’t find a solution to the issue when you encountered it


TL;DR; don’t try to use hooks in class components. All components defined with defsc are class components.


Yeah that was my takeaway @hmaurer, that it wasn’t going to work with defsc’s as they’re ultimately React classes

Chris O’Donnell13:05:59

And call your function components with dom/create-element instead of directly as functions as you would in reagent.

Chris O’Donnell13:05:13

^ This was the real sticking point for me.


well with reagent you don’t quite call them directly @codonnell; you use a vector syntax, e.g. [my-app & props]


so reagent is free to call create-element when interpreting that vector

Chris O’Donnell13:05:39

Sure. 🙂 It makes the call to create-element less obvious, I suppose.


yup. Also note that you could do this in vanilla js as well: just call functions that return JSX to get your app. But component boundaries help react with optimisations, etc


so in short, direct use in defsc’s is a no-op, but (dom/create-element func-component props) is the work around? i’m wondering how well that will ‘scale’ in terms of making the most of fulcro, etc


in theory we could move to a defsc that uses hooks instead of defining a class component


@tony.kay mentioned this before


I am not sure how high it is on his todo-list though


in the meantime, I guess a work-around would be to wrap your function component into a class component defined with defsc for the sole purpose of running a query and providing the data as props to the function component


then you can use hooks as you please in the function component


this looks like an issue I’m trying to find a solution for


I’m not using fulcro (yet); but I’m looking for a way to hydrate a reactified component from a clj(c) rendered component… from a clj server backend


and running into these kind of issues… i.e. having a reactified flavour of hiccup that works in clj


@rickmoynihan Fulcro’s dom stuff is isomophic to clj/cljs…you don’t need a hiccup thingy…just use the functions. It’s an idential amount of typing.


see the book


I’m shooting for F3 to let you actually run the app in clj (full-stack with a “loopback remote”)…no real DOM, of course, but running mutations and loads to get it into the right state to spit out the right thing is on my list.


@codonnell Hooks are really easy to get working, but you cannot use defsc (because that generates a class with lifecycles)…you have to understand the internals a bit. They’ll be a lot easier to use from F3 as well.


Sounds interesting… what do you mean by “loopback remote”?


a remote that doesn’t really do anything remote; it just hits the server’s API directly internally


instead of going through the standard HTTP gateway


e.g. it could directly call the router


ok — so the kind of shimming I’m talking about


I am putting words into @tony.kay’s mouth though; hope I’m right 😅


It could either be literally a loopback interface to your real API (via…kind of wasteful but simulates everything… or more likely a “remote” that just had a handle to the server-side parser that processes the queries.


but it’s still running in the same JVM process? i.e. no need for node on the server?


JVM everything

👍 4

@codonnell Here is an example I did while playing with them for F3…none of this is part of the real code base, but this was working in my playground:

(defn use-fulcro
    "React Hook to simulate hooking fulcro state database up to a hook-based react component."
    [component query ident-fn initial-ident]
    (let [[props setProps] (use-state {})]                  ; this is how the component gets props, and how Fulcro would update them
      (use-effect                                           ; the empty array makes this a didMount effect
        (fn []
          (set! (.-fulcro_query component) query)           ;; record the query and ident function on the component function itself
          (set! (.-fulcro_ident component) ident-fn)
          ;; pull initial props from the database, and set them on the props
          (let [initial-props (prim/db->tree query (get-in @app-db initial-ident) @app-db)]
            (setProps initial-props))
          ;; Add the setProps function to the index so we can call it later (set of functions stored by ident)
          (index! initial-ident setProps)
          ;; cleanup function: drops the update fn from the index
          (fn []
            (drop! initial-ident setProps)))
        #js [])
NOTE: THIS DOES NOT WORK IN F2 OR F3…it worked in my playground env for experimenting with the idea of hooks-based Fulcro…but is shows the approximate level of complexity needed to get it working: pretty low. There, of course, is some work to do making the rest of the platform to understand that they are components, but that isn’t too hard.


@rickmoynihan Fulcro has had server-side rendering support on JVM for years


about 3, I think


F2 requires you use the low-level mutations and manipulate the app state into place, then render. The limitation is that you can’t call “load” and such, because the plumbing has some details that don’t work on the JVM…BUT, it works quite well since mutations can easily be CLJC. F3's plumbing is simpler and will work on both easily.


I don’t know that I’ll implement lifecycle simulation (e.g. componentDidMount that has loads) on the server, so chances are you’ll still want the old way for most apps: make state, morph it, render


though really, just that one lifecycle method probably wouldn’t be that hard to simulate yourself.


@tony.kay interesting… perhaps you can dissuade me of my ill-formed biases against fulcro 🙂 At first glance it seems like it’s a pretty full stack solution; i.e. requires an all or nothing buy in… IIRC your docs talk about graph databases etc. I’m assuming that database isn’t a “real database” (not to be pejorative); am I right that it’s just a transitory one to manage the flow of data between front and backend? i.e. it’s not feasible for us to change database.


@rickmoynihan it depends on your definition of real database; it’s an in-browser datastructure that serves as a cache for the data loaded from the server. It’s completely unrelated to whatever database you might be using on the server


it’s not persistent either; it’s an in-memory datastructure


:thumbsup: ok cool that’s fine it’s not trying to be the single source of truth for my app


all Fulcro components pull their data from that datastructure


it’s the single source of truth in the browser


but you can load data from wherever into it


Have you used Relay? (Facebook’s GraphQL client). Or redux?


Not used relay or redux… but I’m vaguely familiar with the ideas. Have implemented some graphql.


The basic idea is that you want a place to put the data you load from the server. Fulcro normalises the data you load as well, which means that if you load information about the same conceptual “entity” from a server (“remote” in Fulcro’s parlance), say from a single “person”, Fulcro will store it together. This helps keep things consistent


Fulcro’s “graph database” is really just a big map


it has none of the features you would traditionally associate with the word “database”


call it a “normalised data cache”


I mentioned Relay because they do the exact same, albeit in a more obscure way


And Redux doesn’t normalise by default but some librairies let you do so, e.g.


yeah those two can be easy to mix up in casual conversation 🙂


> At first glance it seems like it’s a pretty full stack solution; i.e. requires an all or nothing buy in… Somewhat I guess. As far as I know (at least from my use-case), Fulcro doesn’t really appear on the back-end. It encourages you to use Pathom as a layer between the client requests and your business logic (which serves a similar purpose as GraphQL), but you are not obliged to (though you should really try it out, it’s awesome)


Here’s the basic central concept that you need to grok about Fulcro: It’s central job is to make it easy for you to deal with the graph of data. There are these core tasks: 1. Getting the data from the server 2. Getting data into the UI tree 3. SHARING data among the elements in the tree. Fulcro does this mostly for you by defining a few very simple concepts/ideas. IDENT: A component tells Fulcro “here’s which table my data lives in, and here’s how to derive the ID of that when you see data from a server. QUERY: A component tells Fulcro “here’s the stuff I need, and I also want to render these you’ll need to have their stuff too”

(defsc Child [this props]
  {:ident (fn [] [:child-table-name (:child/id props)])
   :query [:child/id :child/name]} ...)

(defsc Parent [this props]
  {:ident (fn [] [:parent-table-name (:parent/id props)]
   :query [:parent/id :parent/data {:relation (get-query Child)}]})


I was going to keep talking but I’ll let @tony.kay take it from there 😬😄


Now, Fulcro can solve all of the problems with just this data: 1. Load. It can form a graph query to get Parent (and child because it was composed in)


The server returns something like {:parent/id 1 :relation {:child/id 2 :child-name "A"}}


Fulcro can use it’s knowledge of the shape of this to NORMALIZE the child INTO a table…same with parent. I just writes (into app db on browser):


{:parent-table-name {1 {:parent/id 1 :relation [:child-table-name 2]}}
 :child-table-name {2 {:child/id 2 :child/name "A"}}}


Now, to hydrate the UI, it can do the reverse…again, using the query, it can denormalize back into the original tree


cool - how does it pass the data to the client for hydration? data-attr, ajax call?


Then for SHARING: Since things are normalized, you can have 20 diff components that all say they come form the “child table”, and they automatically share data


this is all client


the only thing that came from an imagined server was the graph response to the query


{:parent/id 1 :relation {:child/id 2 :child-name "A"}}


ok — but if the component has been rendered SSR you need to send the initial state to hydrate it somehow, so it can render the next state right?


The book explains all of this, and I’m quite busy…I’ll let the book and perhaps Henri take it from here 🙂


😀 thanks for your help


SSR is “render the first frame fast”, then let the client take over…you just normalize the client browser db and embed it as data in the initial page


@rickmoynihan yeah hydrating is pretty easy, I have done it before


See the SSR chapter


e.g. you could load some data on the backend, shove it in a JS object and render it as part of the page


then Fulcro can pick it up on load and move it into its “graph database” (its cache)


yes I know 🙂 that’s what I was asking… where is the initial state serialised?


I have personally only done that for auth data (a map of info about the current user) because I didn’t want to load that async


but you could do it for anything…


👀 at SSR chapter


> where is the initial state serialised? what do you mean?


it’s not serialised anywhere; by default there is no initial state (well, not server-supplied; every component can define its own initial state)


if you want to server-supply some initial state you can easily do it using Fulcro’s primitives


but it’s not going to pick it up for you from some magic location, if that’s what you are wondering


you’ll have to write some code that 1/ retrives initial state from some JS variable 2/ shoves it into Fulcro’s database


ok cool. That’s effectively what I’ve done already with my home-grown reagent SSR solution.


it shouldn’t feel too foreign to you then, beyond the initial Fulcro learning curve


I also agree the server can’t do it automatically… or rather if it does, it’s probably doing too much.


but it’s good to have the primitives to do it… which is really what I’m looking for.


it could, but @tony.kay’s philosophy with Fulcro (as far as I can tell) is to provide primitive operations and not be too opinionated about those things


I was writing transit json into a data-attr


thanks I have it open 🙂


> I was writing transit json into a data-attr yeah you could do that