Bindings to Heroicons react not working

I’m trying to use the @heroicons/react package. There are bindings to it in rescript-heroicons but when I try to use them in this little App:

module App = {
  type state = Initializing | Done

  @react.component
  let make = () => {
    let (state, setState) = React.useState(() => Initializing)

    React.useEffect0(() => {
      Js.Global.setTimeout(() => {setState(_ => Done)}, 1_000)->ignore
      None
    })

    switch state {
    | Initializing =>
      <p style={lineHeight: "0.8rem"}>
        <Heroicons.Outline.ChevronDoubleDownIcon className="h-6 w-6 animate-bounce" />
        <span> {React.string("Initializing")} </span>
      </p>

    | Done =>
      <p style={lineHeight: "0.8rem"}>
        <Heroicons.Solid.CheckCircleIcon className="h-6 w-6" />
        <span> {React.string("Done")} </span>
      </p>
    }
  }
}

ReactDOM.querySelector("#app")
->Belt.Option.map(rootElement => {
  let root = ReactDOM.Client.createRoot(rootElement)
  ReactDOM.Client.Root.render(root, <App />)
})
->ignore

I get errors like:

react-jsx-runtime.development.js:87 Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `Index$App`.
    at Index$App (http://localhost:2345/aleph/index.js:24593:23)
printWarning @ react-jsx-runtime.development.js:87
error @ react-jsx-runtime.development.js:61
jsxWithValidation @ react-jsx-runtime.development.js:1244
jsxWithValidationDynamic @ react-jsx-runtime.development.js:1301
Index$App @ index.js:51
renderWithHooks @ react-dom.development.js:16305
mountIndeterminateComponent @ react-dom.development.js:20074
beginWork @ react-dom.development.js:21587
beginWork$1 @ react-dom.development.js:27426
performUnitOfWork @ react-dom.development.js:26557
workLoopSync @ react-dom.development.js:26466
renderRootSync @ react-dom.development.js:26434
performConcurrentWorkOnRoot @ react-dom.development.js:25738
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533
react-dom.development.js:28439 Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

...

First I thought that it was about rescript-heroicons being configured for JSX3; but I took the code and embedded it in my project without success.

I don’t see anything strange in the components definitions (but I’m also new to both React and @rescript/react).

Any ideas?

I just set up a tiny demo app using the heroicons bindings and it worked fine with react jsx v3. Can you post more of your setup (eg package.json, bsconfig, etc.)? (Or make a github repo for it?)


For reference, here’s the setup I did…

package.json

{
  "name": "rescript-project-template",
  "version": "0.0.1",
  "scripts": {
    "res:build": "rescript",
    "res:clean": "rescript clean",
    "res:dev": "rescript build -w",
    "start": "vite"
  },
  "keywords": [
    "rescript"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@heroicons/react": "^1.0.6",
    "@rescript/react": "^0.11.0",
    "@vitejs/plugin-react-refresh": "^1.3.6",
    "rescript": "*",
    "rescript-heroicons": "^0.0.3",
    "vite": "^4.2.1"
  }
}

vite.config.cjs

import { defineConfig } from "vite";
import { resolve } from "path";
import reactRefresh from "@vitejs/plugin-react-refresh";

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, "index.html"),
      },
    },
  },
  plugins: [reactRefresh({ include: "**/*.mjs", exclude: "/node_modules/" })],
});

bsconfig.json

{
  "name": "rescript-project-template",
  "sources": {
    "dir": "src",
    "subdirs": true
  },
  "package-specs": {
    "module": "es6-global",
    "in-source": true
  },
  "suffix": ".mjs",
  "reason": { "react-jsx": 3 },
  "bs-dependencies": ["rescript-heroicons", "@rescript/react"]
}

Note that I used the rescript project template, and made the changes you see there, in addition to changing the name of the Demo.res to Index.res (to go with the vite config).

@Ryan

I’m trying to solve the issue by updating the package here GitHub - mvaled/rescript-heroicons at jsx4

I added a small index.html, and index.res there. But it actually works! So, I guess it’s something about my app bsconfig or something.

I think I need some rest, now it doesn’t work!

@Ryan Can you check that branch of the project and see it works for you?

I add a script run (i.e npm run run) to easily run the test App in http://localhost:8000. This is what I get:

I don’t see anything funny in the index.bs.js.

Perhaps there’s a version mismatch. Are you using version 1~ of @heroicons/react?

Would also be interesting to see what the contents are for the imported icons in your generated JS-file.

Hi @lessp

This is the package.json. I was using @heroicons/react, version 2. Changing it to ^1.0, makes it work!

Thanks!

1 Like

At first I was kind of surprised that the generated Outline.bs.js and Solid.bs.js were mostly empty:

// Generated by ReScript, PLEASE EDIT WITH CARE

var AcademicCapIcon = {};

var AdjustmentsIcon = {};

var AnnotationIcon = {};

var ArchiveIcon = {};

var ArrowCircleDownIcon = {};

var ArrowCircleLeftIcon = {};

/// etc....

But apparently, that’s just optimized to the call-site. The index.bs.js looks like:

// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Curry from "rescript/lib/es6/curry.js";
import * as React from "react";
import * as Belt_Option from "rescript/lib/es6/belt_Option.js";
import * as Caml_option from "rescript/lib/es6/caml_option.js";
import * as Client from "react-dom/client";
import * as JsxRuntime from "react/jsx-runtime";
import * as Solid from "@heroicons/react/solid";
import * as Outline from "@heroicons/react/outline";

function Index$App(props) {
  var match = React.useState(function () {
        return /* Initializing */0;
      });
  var setState = match[1];
  React.useEffect((function () {
          setTimeout((function (param) {
                  Curry._1(setState, (function (param) {
                          return /* Done */1;
                        }));
                }), 1000);
        }), []);
  if (match[0]) {
    return JsxRuntime.jsxs("p", {
                children: [
                  JsxRuntime.jsx(Solid.CheckCircleIcon, {
                        className: "h-6 w-6"
                      }),
                  JsxRuntime.jsx("span", {
                        children: "Done"
                      })
                ],
                style: {
                  lineHeight: "0.8rem"
                }
              });
  } else {
    return JsxRuntime.jsxs("p", {
                children: [
                  JsxRuntime.jsx(Outline.ChevronDoubleDownIcon, {
                        className: "h-6 w-6 animate-bounce"
                      }),
                  JsxRuntime.jsx("span", {
                        children: "Initializing"
                      })
                ],
                style: {
                  lineHeight: "0.8rem"
                }
              });
  }
}

var App = {
  make: Index$App
};

Belt_Option.map(Caml_option.nullable_to_opt(document.querySelector("#app")), (function (rootElement) {
        var root = Client.createRoot(rootElement);
        root.render(JsxRuntime.jsx(Index$App, {}));
      }));

export {
  App ,
}
/*  Not a pure module */

So the import is being done in the index instead.

For this commit: d415a6a476604fa33f240bf6ae33035d1837101c, I did the npm run run (after installing esbuild) and the app worked without error…maybe you have fixed it since you sent this message, but just wanted to let you know anyway.

Ahh whoops, I forgot to call that out as one of the things I needed to get it working originally…sorry about that!

1 Like

FWIW, version 2 seems to be using e.g. import { BeakerIcon } from '@heroicons/react/24/solid' i.e. the 24/20 is different than before.

1 Like

That works! Commit Update to '@heroicons/react' v2. · mvaled/rescript-heroicons@b0b2121 · GitHub

cc: @Ryan

1 Like

@Ryan @lessp

I updated the branch to allow more props Allow to use all standard props. · mvaled/rescript-heroicons@c596407 · GitHub

It’s a big commit, because I need to use the ‘render’ function directly.

Do you see something suspicious?

Not familiar with JsxDOM.domProps, but instinctively I’d do e.g.

module CakeIcon = {
  @module("@heroicons/react/24/outline") @react.component
  external make: JsxDOM.domProps => React.element = "CakeIcon"
}

That was my first try but it didn’t work. It would not compile:

<Solid.CakeIcon className="w-6"/>

claiming className was not allowed.

So, @react.component cannot be used in that case. But then, the exported CakeIcon is an object and we need make to be a function.