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
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.
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.