This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-05-27
Channels
- # announcements (2)
- # beginners (85)
- # boot (4)
- # calva (4)
- # cider (14)
- # cljdoc (8)
- # cljs-dev (5)
- # cljsrn (10)
- # clojure (101)
- # clojure-europe (1)
- # clojure-italy (6)
- # clojure-nl (12)
- # clojure-spec (4)
- # clojure-uk (71)
- # clojurescript (119)
- # core-async (20)
- # cursive (1)
- # datascript (2)
- # duct (3)
- # emacs (19)
- # fulcro (150)
- # graphql (1)
- # hoplon (2)
- # instaparse (2)
- # jobs (1)
- # jobs-discuss (11)
- # joker (9)
- # luminus (6)
- # lumo (1)
- # off-topic (33)
- # onyx (1)
- # quil (1)
- # re-frame (23)
- # reagent (11)
- # robots (2)
- # rum (6)
- # sql (1)
- # test-check (10)
- # unrepl (1)
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. 🙂
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() : little_gift_list.ui.root.use_styles.call(null));
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();
@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.
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
.what’s your code with defn
@codonnell?
Yeah, material ui
(defn use-styles []
(styles/makeStyles (clj->js {:root {:width 500}})))
Ah, you're right.
but still, looking at the material-ui JS code it seems to match what you did with def
No, I think the def
was wrong; makeStyles
takes a function as its argument that returns a map.
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?
No, stopped working yesterday once I got it to compile. Take a look at a component example: https://material-ui.com/components/buttons/. 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
@codonnell can you copy/paste (or gist if it’s long) the code you tried to run?
or more directly, answer this question: did you try to call use-styles
within a component defined with defsc
?
One sec, converting this into a more manageable example (and closing some tabs so my old laptop stops complaining so much).
It is in a function component rather than inside a defsc
(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)}))))
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?
(defsc Root
[this {:keys [router header]}]
{}
(my-nav))
^ The root component, if it wasn't clear.
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”
the defn
didn’t throw because it wasn’t calling the hook, but I would bet it wasn’t changing the styles either
Ah, I need to wrap it with react/createElement
don't I
There it is. 🙂
Thank you!
@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
And call your function components with dom/create-element
instead of directly as functions as you would in reagent.
^ 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]
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
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
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.
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
ok — so the kind of shimming I’m talking about
It could either be literally a loopback interface to your real API (via 127.0.0.1)…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?
@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 [])
props)))
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.👍 Thanks.
@rickmoynihan Fulcro has had server-side rendering support on JVM for years
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
:thumbsup: ok cool that’s fine it’s not trying to be the single source of truth for my app
even better
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
it has none of the features you would traditionally associate with the word “database”
And Redux doesn’t normalise by default but some librairies let you do so, e.g. https://github.com/paularmstrong/normalizr
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 children..so 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)}]})
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
the only thing that came from an imagined server was the graph response to the query
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
e.g. you could load some data on the backend, shove it in a JS object and render it as part of the page
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
👀 at SSR chapter
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.
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 🙂