Fork me on GitHub
#reagent
<
2022-09-21
>
jcb15:09:31

Hi me again, struggling with a translation issue from a react example into reagent hiccup. I can't quite understand how the props example given in the docs would translate from something like this -

jcb15:09:41

const Page = React.forwardRef((props, ref) => {
    return (
        <div className="demoPage" ref={ref}>
            /* ref required */
            <h1>Page Header</h1>
            <p>{props.children}</p>
            <p>Page number: {props.number}</p>
        </div>
    );
});

function MyBook(props) {
    return (
        <HTMLFlipBook width={300} height={500}>
            <Page number="1">Page text</Page>
            <Page number="2">Page text</Page>
            <Page number="3">Page text</Page>
            <Page number="4">Page text</Page>
        </HTMLFlipBook>
    );
}

jcb15:09:49

it seems simple, but I can't quite figure it with the r/current-component situation

p-himik15:09:02

What have you tried so far? And why do you even need r/current-component?

jcb15:09:04

I'm trying to explicitly pass the props as in the example which is not something I've had to think about with reagent before

p-himik15:09:46

The React code that you've provided is written using Reagent as simple as:

(defn page [{:keys [ref number]} & children]
  [:div {:class :demoPage, :ref ref}
   [:h1 "Page Header"]
   (into [:p] children)
   [:p "Page number: " number]])

(defn my-book []
  [:> HTMLFlipBook {:width 300, :height 500}
   [page {:number 1} "Page text"]
   [page {:number 2} "Page text"]
   [page {:number 3} "Page text"]
   [page {:number 4} "Page text"]])
Haven't tested but I'm pretty sure that that ref will work just fine if you actually use it.

jcb15:09:45

Thank you for spending time and taking a look. I don't totally understand why that works though, is the link I was referencing outdated?

p-himik15:09:54

It's not. It's just that you don't need to access the native props in your example.

p-himik15:09:50

You shouldn't try translating things verbatim - it will invariably end bad in many cases because you will be using JS/React idioms in the CLJS/Reagent world. Instead, think of what's going on and how to replicate the behavior in Reagent.

jcb16:09:12

Yes, I just find that difficult when I attempt to use react components from libraries

p-himik16:09:05

Then the best approach would be to ask a more concrete question, including what library you're trying to use and what code you have come up with so far. More often than not the solution is something extremely simple, like adding an extra as-element or preventing props serialization.

jcb16:09:54

Fair enough, I really do appreciate that you took the time to help. In these situations I try to ask the least specific thing that seems to be the issue, so that I can understand the overall concept. Otherwise the friction that I'm getting here will reoccur every time I attempt something similar.

jcb16:09:05

The library that I'm trying to use in this instance is here - https://www.npmjs.com/package/react-pageflip it creates a bit of animation

jcb16:09:33

currently getting an error - React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined etcetc

p-himik16:09:28

That error is meaningless without the code that produced it. ;)

jcb16:09:12

Sorry, it's essentially what you gave me

jcb16:09:24

(defn p [{:keys [ref number]} & children]
  (fn []
    (let [!page (r/atom nil)]
     [:div {:class :demoPage, :ref (fn [ref] (reset! !page ref))}
      [:h1 "Page Header"]
      (into [:p] children)
      [:p "Page number: " number]])))

(defn my-book []
  [:> pf/HTMLFlipBook {:width 300, :height 500}
   [p {:ref 1 :number 1} "Page text"]
   [p {:ref 2 :number 2} "Page text"]
   [p {:ref 3 :number 3} "Page text"]
   [p {:ref 4 :number 4} "Page text"]])

p-himik16:09:12

Ah, crap - HTMLFlipBook uses React.cloneElement and assigns its own ref under the hood. Really hate stuff like that.

jcb16:09:21

It's an ugly thing in general 😅

jcb16:09:36

would I pass the forwardRef function to the p component in the :ref key?

p-himik16:09:44

Not sure what you mean. In your case, it should be something like

(def page
  (react/forwardRef
    (fn page [^js props ref]
      (r/as-element [:div {:ref ref}
                     [:p (.-children props)]  ;; Try `(into [:p] ...)` if this doesn't work.
                     ...]))))

jcb17:09:54

Now I'm getting a strange error invalid hiccup tag

jcb17:09:00

Error: Assert failed: Invalid Hiccup form: [#js {"$$typeof" "Symbol(react.forward_ref)"

p-himik17:09:40

Right, that page is a React component and has to be used like one. But Reagent itself would wrap those [page ...] Hiccup vectors with as-element. And you don't need that wrapping. One way to do it would be to use r/create-element inside a React component wrapped in a Reagent component, something like

(defn -my-book []
  (r/create-element pf/HTMLFlipBook
                    #js {:width 300, :height 500}
                    (r/create-element page #js {:number 1} "Page text")
                    ...))

(defn my-book []
  [:> -my-book])
Maybe there's a better way, not sure.

jcb17:09:48

Wow, this is really interesting! sorry to drag you along for the ride

jcb17:09:09

So without using the forwardRef at all?

p-himik17:09:30

Oh, with it - it's still there in page.

jcb18:09:48

sorry, only just had a chance to try and I'm still getting Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

jcb18:09:49

Are there any tools that can help diagnose what's happening with the import? If i (js/console.log pf) which is the library, it returns an object which is itself a react forwardRef. But the supposed component class is nowhere to be found i.e HTMLFlipBook isn't available within it - not even hidden behind pf/default

p-himik18:09:44

Your browser's debugger is the best thing here. You can tell it to break on an exception - that's how I'd do it.

p-himik18:09:31

If you won't be able to make it work - create a minimal reproducible example that includes all the steps necessary to build and run it and I'll take a look.

Alexis Schad15:09:37

Hi, React and npm interops are quite confusing, I also used to struggle with it too although the final solution is often simple. There are few mistakes in the snippet you sent. First, react-pageflip is in common js style, so you have to import it like ["react-pageflip" :as HTMLFlipBook] . That's why you get the "but got: undefined" error. Second, react/forwardRef needs a react component, so you have to transform the hiccup form with r/as-element like this :

(def Page
  (react/forwardRef
   (fn [props ref]
     (r/as-element
      [:div.demoPage {:ref ref}]))))

Alexis Schad15:09:16

Finally, as react/forwardRef gives you a React component, you have to use it as one:

[:> HTMLFlipBook {:width 300 :height 500}
   [:> Page {:number 1} "Page text"]
   ; others pages

Alexis Schad17:09:11

Since it's been two days I ping you @U2EJEK2SX else I'm not sure how quickly you would see this

jcb13:09:42

Hi @U01V2D5ALKX, thank you for taking the time to explain, really informative, cheers!