0

I'm trying to use react-bootstrap within re-frame project. I've installed react-bootstrap with

npm install react-bootstrap

and using its components like the following:

  (:require
   ;; ...
   ["react-bootstrap/Button" :as Button]
   ;; ...

(defn main-panel []
  [:div
   [:> Button "Hit me"]
  ]])

Everything works fine until I try to make a dropdown, to be more accurate, until I try to use DropdownMenu. The moment I insert it into the hiccup following the example like that

[:> Dropdown
  [:> DropdownToggle "button"]
  [:> DropdownMenu {:variant :dark}
    [:> DropdownItem "action1"]
    [:> DropdownItem "action2"]
    [:> DropdownItem "action3"]]]

I'm getting the following in the browser console:

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

I'm awfully new to the whole frontend world, so I'm not sure if I'm doing something wrong or there is some bug in react-bootstrap, or reagent, or any other part of the project. Here's the MWE of this problem: https://github.com/lockie/react-bootstrap-cljs-demo

Andrew Kravchuk
  • 356
  • 1
  • 5
  • 17
  • I have no idea about closerscript, but aren't you missing a Dropdown.Toggle component in your code? Do you have a runnable MWE which I could run instantly without setup? – Igor Gonak May 10 '22 at 18:33
  • 1
    Right, I was using DropdownToggle, just forgot to copy it into the code sample. You can run MWE I've posted by running `npm install` followed by `npx shadow-cljs watch app` and then going to http://localhost:8280 – Andrew Kravchuk May 11 '22 at 04:52

1 Answers1

3

The documentation has a section on how to translate ES import for npm packages.

There you'll find that ["react-bootstrap/Button" :as Button] should be ["react-bootstrap/Button$default" :as Button] instead (translating import Button from "react-bootstrap/Button";)

This however only applies when using actual ESM code. The react-bootstrap package however is a hybrid package containing both ESM and CommonJS. shadow-cljs will not use ESM by default in that case.

So one option is to make shadow-cljs use the ESM code instead which is done by including this in your build config.

:js-options {:entry-keys ["module" "browser" "main"]}

This will require using $default for every require since it they are all default exports.

Another option is just using the default CommonJS code. However this appears to be packaged inconsistently, meaning that most just work but there is one exception. It works fine if I change your example code to just

(ns react-bootstrap-cljs-demo.views
  (:require
    [re-frame.core :as re-frame]
    [react-bootstrap-cljs-demo.subs :as subs]
    ["react-bootstrap/Button" :as Button]
    ["react-bootstrap/Dropdown" :as Dropdown]
    ["react-bootstrap/DropdownItem" :as DropdownItem]
    ["react-bootstrap/DropdownMenu$default" :as DropdownMenu]
    ["react-bootstrap/DropdownToggle" :as DropdownToggle]
    ))

So, only the DropdownMenu is included in a way that requires the $default. Seems to maybe an oversight or bug on the react-bootstrap side. shadow-cljs is just following what is on disk.

Which method you choose is up to you. Using ESM might lead to issues with other packages, or may work perfectly fine. All depends on what the packages you use actually published.

How To Figure This Out?

As a little mini guide: I figured this out by just logging the required :as alias an looking at it in the browser console.

So just

(ns react-bootstrap-cljs-demo.views
  (:require
    [re-frame.core :as re-frame]
    [react-bootstrap-cljs-demo.subs :as subs]
    ["react-bootstrap/Button" :as Button]
    ["react-bootstrap/Dropdown" :as Dropdown]
    ["react-bootstrap/DropdownItem" :as DropdownItem]
    ["react-bootstrap/DropdownMenu$default" :as DropdownMenu]
    ["react-bootstrap/DropdownToggle" :as DropdownToggle]
    ))

(js/console.log "Button" Button)
(js/console.log "Dropdown" Dropdown)
(js/console.log "DropdownItem" DropdownItem)
(js/console.log "DropdownMenu" DropdownMenu)
(js/console.log "DropdownToggle" DropdownToggle)

If you remove the $default from DropdownMenu you'll notice that the log line looks different than all the others and that it is an object with a default property. So you add the $default to use that property and get what you actually need.

Thomas Heller
  • 3,842
  • 1
  • 9
  • 9
  • Unfortunately using this `$default` syntax, I'm getting another error, this time for any react-bootstrap component, not just `DropdownMenu`: "Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined." – Andrew Kravchuk May 11 '22 at 08:10
  • Also importing with `:refer`, like this `["react-bootstrap" :refer [Button Dropdown DropdownToggle DropdownMenu]` produces the same `undefined` error. @Thomas perhaps react-bootstrap uses some weird file layout that's incompatible with shadow-cljs? – Andrew Kravchuk May 11 '22 at 09:11
  • 1
    Expanded my answer with more details. The package does appear to be buggy leading to that one weird case. – Thomas Heller May 11 '22 at 09:59