Fork me on GitHub
#reagent
<
2018-07-11
>
minikomi03:07:00

i'm working on a form library for reagent, and it uses a schema for both preparing the dynamic ratom, as well as rendering parts of the form. it was getting messy passing the schema down through everything, so i wanted to create a dynamic var and bind it using a macro. i can't seem to get the binding right though -- my knowledge of what is bound when is not very good. i have a file which does some preparation / state manipulation, where the dynamic schema is bound like:

fields.cljs

(def ^:dynamic *form-schema* {:id "unbound"})

then a clj file with the same namespace, which does the binding:
fields.clj

(defmacro with-schema [form-schema & body]
  (binding [*form-schema* ~form-schema]
    ~@body))

in the frontend file:
(fn render-fields []
  ;; just for demonstration
  [:h1 (:id fields/*form-schema*)]
  )

when i try to use it like:
(fn render-form []
  (with-schema {:id "test"}
    [frontend/render-fields]))

i always get unbound as the id returned. any ideas?

justinlee03:07:00

are you doing all this just to avoid passing a map?

minikomi03:07:57

There's several components people can use -- fields, buttons, debugger for current state of form etc. Rather than pass down a map through several layers of nesting, and since the schema is always the same in the context of the form, a bound dynamic var would clean a lot of it up

justinlee03:07:54

does it work without the macro? i.e., if you just type out the binding? I suspect its a namespace issue

minikomi03:07:30

it works with the macro except when using components defined in other files

minikomi03:07:17

(fn render-form []
  (with-schema {:id "test"}
   [:h1 (:id [fields/*form-schema*])]))

works fine

justinlee03:07:23

i assume the difference between *schema* and *form-schema* is just a slacko?

minikomi03:07:32

ah yeah sorry

justinlee03:07:55

does it work without the macro when using components in other files? i think it would be useful to take the macro out of the picture

minikomi03:07:00

can you explain what that might look like?

minikomi03:07:10

i'm not sure how to do it without a macro

minikomi03:07:38

(fn render-form []
  (binding [fields/*form-schema* {:id "test"}]
    [frontend/render-fields]))

minikomi03:07:34

also, didn't work for me

justinlee03:07:34

(fn render-form [] (binding [forms/*form-schema* {:id "test"}] [frontend/render-fields]))

minikomi03:07:04

trying to type in slack is a pain, not sure when to return or cmd-return

justinlee03:07:31

good okay so are you using fields/*form-schema* everywhere?

justinlee03:07:52

i think you’re probably not referencing the right dynamic var (?)

justinlee03:07:16

dynamic vars aren’t going to work here

minikomi03:07:28

yeah, i use it to prepare a "live" atom state, and also to do a bunch of rendering of different components

justinlee03:07:41

reagent doesn’t run the functions referenced in the vectors in a lexical way

minikomi03:07:02

thought as much.. didn't hurt to ask though

justinlee03:07:47

i mean, it’s going to work in the simple example, but remember that every time you have a (fn parent [] [child blah blah] the child function will be run later

minikomi03:07:49

it's more to show that all the components are linked to the same schema in that context.

justinlee03:07:07

the parent will run and return the vector immediately, then child will be run

minikomi03:07:12

rather than pass the schema around everywhere

minikomi03:07:45

right.. timing of bindings & running of code is tricky to reason about on the front end i guess

minikomi03:07:41

is there another pattern i can use here i wonder..

justinlee03:07:07

it’s particularly hard to reason about with reagent 🙂

justinlee03:07:41

i don’t quite understand what you want to accomplish so I’m not sure what to do to make it work for you

minikomi03:07:21

Right now, using the library is like:

(defn form-component [form-schema]
  (let [state (formic-field/prepare-state form-schema values)]
    [:div "Parent component"
     [:form
      [formic-frontend/fields form-schema state]
      [formic-frontend/buttons form-schema state]
      [formic-frontend/debug form-schema state]
      ]]))
But i wanted to make it like:
(defn form-component [form-schema]
  (formic-field/with-schema form-schema
    (let [state (formic-field/prepare-state values)]
      [:div "Parent component"
       [:form
        [formic-frontend/fields state]
        [formic-frontend/buttons state]
        [formic-frontend/debug state]
        ]])))
To show all was bound within the same schema.

minikomi03:07:04

It has the benefit of cleaning up a lot of internal code too

justinlee04:07:10

so why don’t you just place form-schema on state when you call prepare-state

minikomi04:07:32

yeah, i tried that too. guess that's the way to go.

justinlee04:07:57

seems more functional to me

nijk09:07:37

Does anyone know how to achieve React.forwardRef in reagent? https://reactjs.org/docs/forwarding-refs.html I’m implementing ReactPopper.js using Reagent and it seems to be a sticking point based on this issue: https://github.com/FezVrasta/react-popper/issues/198

kennytilton11:07:56

That forwardRef write-up is funny. To me it says, “Remember when we jumped up and down about component isolation? OK, that was wrong.”

Hukka12:07:01

Any experience/feelings on using https://github.com/mui-org/material-ui?

Hukka12:07:11

I've been trying to migrate away from cljs-react-material-ui, which is stuck in the v0 time. Just today I noticed that someone had made a fork for v1, but that fell on its face. So now I'm thinking if I should use material-ui directly, or perhaps these "official" react components https://github.com/material-components/material-components-web-react

valtteri12:07:57

I’ve been using material-ui from cljsjs. [cljsjs/material-ui "1.2.1-0"] was added there recently.

Hukka13:07:46

Any experience compared to the other options?

valtteri13:07:48

At one point I did some experiments with ‘double bundle’ approach described here. https://clojurescript.org/guides/webpack I was able to consume material-ui from npm quite easily. No problems with that, but I decided to use cljsjs anyway because it’s simpler. I thought I would use all the fancy libs from npm but I found out quite soon that in many cases it’s better to just write your own component instead of spending time learning the api and customizing the component to your needs.

nenadalm17:07:23

I am using material-ui with webpack (I am using v1 since it was beta and it wasn't on cljsjs so it was the only option - other than figuring out how to update cljsjs package)

Hukka12:07:11

I've been trying to migrate away from cljs-react-material-ui, which is stuck in the v0 time. Just today I noticed that someone had made a fork for v1, but that fell on its face. So now I'm thinking if I should use material-ui directly, or perhaps these "official" react components https://github.com/material-components/material-components-web-react

justinlee14:07:56

i don’t use material ui myself, but I would definitely lean towards doing direct interop instead of using a wrapper library

justinlee14:07:17

with shadow-cljs as your build tool, it can be easy and efficient to use the npm packages directly

justinlee14:07:46

oh i see in the thread you already got webpack to work for you

juhoteperi14:07:50

Instead of adapt-react-class, e.g. [:> mui/MenuItem ...] would also work, without need to create def for every component.

juhoteperi15:07:44

the example doesn't use mui-theme-provider, I'm not sure how that works, but in general it should be easy to use mui with just interop

juhoteperi15:07:14

I guess I could extend the example with some theme settings etc. to show how they work

Macroz15:07:18

@juhoteperi is that :> really required or could native react components be somehow just detected (x.prototype.isReactComponent etc.)? did you think about that?

juhoteperi15:07:07

It is currently required. Reagent doesn't do detection and I have no idea if that would be possible.

Macroz15:07:37

since it already differentiates between keywords and fns it would feel natural for it to detect for the presense of a react component without resorting to adapting

Macroz15:07:54

I can think of reasons why adapting is nice, e.g. only do it once or performance hit

juhoteperi15:07:31

No both adapt-react-class and :> have same performance

Macroz15:07:31

reading the official React docs I don't see the function isReactComponent that is possibly there anyway

Macroz15:07:53

I mean those have better performance compared to detecting so no adapt-react-class or :> like I suggested

juhoteperi15:07:08

there isn't really any conversion, well, except for parameters, but that happens in both cases

juhoteperi15:07:43

adapt-react-class creates a Cljs type and Reagent render code has case for that type which will call react/createElement, and :> does the same

juhoteperi15:07:06

I'm not sure if that helps with e.g. functional components which are just normal functions

Macroz15:07:20

yes those need another specific check

juhoteperi15:07:33

Normal Reagent components are just functions

Macroz15:07:35

just wondering if that extra :> syntax is required

juhoteperi15:07:47

There is no way to see if a function is Reagent component or functional React component

Macroz15:07:02

also one could assume that if it's not keyword or regular (ClojureScript) fn it's a react component?

juhoteperi15:07:19

ClojureScript fn is JavaScript fn

juhoteperi15:07:03

One cljs is compiled to JS there is no way to see if the function was created from Cljs or from React lib

Macroz15:07:36

functions are objects that can have extra properties?

juhoteperi15:07:58

Yes, but in this case they don't have

juhoteperi15:07:57

i.e. (defn reagent-component [] [:div "foo"]) and function reactComponent () { return <div>foo</div>; } create same function

juhoteperi15:07:44

IF Reagent has some def component macro like some other wrappers, we could add properties or something

Macroz15:07:19

so what difference does it make to [foo :bar 42] expression in reagent if the foo is React or CLJS?

Macroz15:07:54

native needs to be wrapped ... because?

juhoteperi15:07:23

Native doesn't need to be wrapped. Native is marked using a type or :> as it can be passed directly to React.

juhoteperi15:07:46

Reagent functions need to be wrapped in lifecycle methods which handle ratoms.

Macroz15:07:24

and basically :> and adapt-react-class skips that

juhoteperi15:07:41

Reagent implementation is quite complex 🙂 The implementation could be much simpler if Reagent used some macros to generate components or to wrap function bodies. But that wouldn't be Reagent.

Macroz15:07:45

thanks for the explanation

Macroz15:07:08

just wondering if there would have been a way to distinguish automatically and avoid the spurious :> "syntax"

Macroz15:07:37

something that advanced compilation wouldn't throw away too 🙂

Macroz15:07:51

I think easy interop with the JS world is a must

Macroz15:07:16

not enough Clojurians to solve horde level problems like browser compatibility 🙂

justinlee15:07:57

@macroz adding the :> is not the hard part of javascript interop 🙂

juhoteperi15:07:59

Seems like with latest Cljs it is possible to use real npm names for Material UI:

["@material-ui/core" :as mui]
            ["@material-ui/core/styles" :refer [createMuiTheme]]
            ["@material-ui/icons" :as mui-icons]
This will make it easy to later use node modules which will enable DCE for Mui.

Macroz15:07:31

@justinlee it's not, but the wrapping issue it's part of is though, nobody maintains wrappers etc.

juhoteperi15:07:14

I'll have extensive no-wrapper Material-UI example online soon

justinlee15:07:14

what i’m saying is, interop is a top goal for me, but worrying about a two-character piece of syntax is literally last on the list of things that makes it hard

justinlee15:07:23

just getting npm packages to import correctly is hard enough. then there is the fact that reagent isn’t a straight mapping to react at all and you’ve got to learn quite a bit about it’s subtleties to get interop with more complex packages working. the :> is easy enough

Macroz15:07:45

yes there are other problems, like the libs requiring JS-specific tooling to enable customization, theming etc.

Hukka16:07:32

@juhoteperi Hm. Then, is there any reason to use cljsjs-material-ui anymore?

justinlee16:07:59

@juhoteperi that’s giving me a 404

Hukka16:07:44

"Needs compiler fixes"?

juhoteperi16:07:33

That refers to the commented requires, with real npm module names

juhoteperi16:07:33

the current names wouldn't work with npm packages, ClojureScript compiler currently has at least two bugs: 1. preventing using of scoped npm packages with node_module support 2. foreign-libs providing and using global-exports don't with if name has @

Hukka16:07:32

I'll stick to cljsjs then

justinlee17:07:30

@tomi.hukkalainen_slac you can’t use scoped packages with cljsjs either. just use the normal includes