Fork me on GitHub
#shadow-cljs
<
2021-06-24
>
erwinrooijakkers08:06:31

I have a JSX component that returns a React component:

import React from "react";
import Select from "react-select";
import Survey from "survey-react";

const CustomSearchWidget = ({ choices, onChange }) => {
...
  return (
    <Select
      ...
    />
  );
};

export const SearchWidget = {
  ...
  render: (question) => {
    return (
      <CustomSearchWidget
        ...
      />
    );
  },
};
When I compile this using babel with npx babel *.jsx --out-dir gen and import this compiled file in a clojurescript file like so:
["./surveyjs/widgets/gen/SearchWidget.js" :refer [SearchWidget]]
and use the SearchWidget in the application I get this error:
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object
If I return <div/> instead of <Select/> this error does not occur

erwinrooijakkers09:06:24

Full code:

import React from "react";
import Select from "react-select";
import Survey from "survey-react";

const CustomSearchWidget = ({ choices, onChange }) => {
  const handleChange = (option) => {
    onChange(option.value);
  };

  const options = choices.map((choice) => {
    return { label: choice, value: choice };
  });

  return (
    <Select
      className="basic-single"
      classNamePrefix="select"
      isClearable={true}
      isSearchable={true}
      name="color"
      options={options}
      onChange={handleChange}
    />
  );
};

export const SearchWidget = {
  name: "search dropdown",
  title: "Dropdown with search",
  isFit: function (question) {
    return question.getType() === "searchdropdown";
  },
  activatedByChanged: function (activatedBy) {
    Survey.JsonObject.metaData.addClass("searchdropdown", [], null, "text");
    Survey.JsonObject.metaData.addProperties("searchdropdown", [
      { name: "choices", default: ["Click Me"] },
    ]);
  },
  render: (question) => {
    return (
      <CustomSearchWidget
        choices={question.choices}
        placeholder={question.title}
        onChange={(val) => (question.value = val)}
      />
    );
  },
};

erwinrooijakkers09:06:00

(And it goes wrong when importing like so):

(ns nl.mediquest.questionnaire-frontend.component.survey
  (:require
   ["./surveyjs/widgets/gen/SearchWidget.js" :refer [SearchWidget]]
   [survey-react :refer [Survey StylesManager CustomWidgetCollection]]))

(.. CustomWidgetCollection -Instance (addCustomWidget SearchWidget "customtype"))

erwinrooijakkers09:06:56

package.json

{
  "devDependencies": {
    "@babel/cli": "^7.14.5",
    "@babel/core": "^7.14.6",
    "@babel/plugin-transform-react-jsx": "^7.14.5",
    "shadow-cljs": "2.10.12"
  },
  "dependencies": {
    "@fortawesome/fontawesome-pro": "^5.13.0",
    "@fullhuman/postcss-purgecss": "^2.0.5",
    "autoprefixer": "9.8.0",
    "global": "^4.4.0",
    "postcss-cli": "7.1.1",
    "postcss-import": "^12.0.1",
    "react": "^16.13.0",
    "react-dom": "16.13.0",
    "react-select": "^4.3.1",
    "recharts": "1.8.5",
    "survey-react": "^1.8.4",
    "tailwindcss": "1.4.6"
  },
  "scripts": {
    "build-css": "node_modules/postcss-cli/bin/postcss resources/public/css/tailwind.css -o resources/public/style.css"
  }

erwinrooijakkers09:06:07

it is something with the Select component, because when I turn it into <div/> the webpage renders and there are no errors

erwinrooijakkers08:06:38

Is there a workaround for this?

thheller08:06:16

@erwinrooijakkers you ... out a bunch of code so I cannot see what this SearchWidget actually is. from what you left in it is not a correct react component and that is what the error is telling you?

thheller08:06:36

maybe you just forgot a export const SearchWidget = class extends React.Component {?

erwinrooijakkers09:06:36

Thank you. I added the full code in the reply

erwinrooijakkers09:06:42

export const SearchWidget = class extends React.Component { gives babel compilation error:

Unexpected token (28:6)

  26 |
  27 | export const SearchWidget =  class extends React.Component {
> 28 |   name: "search dropdown",
     |       ^

erwinrooijakkers09:06:08

with npx babel *.jsx --out-dir gen

erwinrooijakkers09:06:23

these are functional components I understand from the party that helped made them, so cannot add a class extends React.Component, although they say in the end itโ€™s the same

thheller09:06:18

as the error is telling you an object is not a valid react component

thheller09:06:41

so whatever it is it is invalid. it has nothing to do with shadow-cljs, it is purely a react error

thheller09:06:08

maybe its meant to be a React.createClass({ name: ...})

erwinrooijakkers09:06:14

I fixed it by changing:

import Select from "react-select";
to
const Select = require('react-select');

erwinrooijakkers09:06:35

the same code did work in javascript for some reason

erwinrooijakkers09:06:42

in the create-react-app

erwinrooijakkers09:06:49

thanks for the help!

Alex J Henderson10:06:59

I'm using shadow-cljs, with the helix library. I'm noticing that a tagged js literal such as #js [] when placed anywhere within the form of a call to react/useState, breaks the hooks state preservation on hot reload. For example test-state is preserved if I make a code change with this component:

(defnc test-component []
  (let [[test-state set-test-state] (react/useState (do [] 1))]
    ($ "button" {:on-click #(set-test-state inc)} (str "test1: " test-state))))
but if I tag the array inside the do form with a #JS like the following, the state of test-state is not preserved if I make a code change:
(defnc test-component []
  (let [[test-state set-test-state] (react/useState (do #js [] 1))]
    ($ "button" {:on-click #(set-test-state inc)} (str "test1: " test-state))))
maybe this is not a shadow-cljs issue, but any help would be much appreciated, thanks!

thheller11:06:15

why is there a do in the first place?

Alex J Henderson12:06:03

simply to demonstrate that it's some sort of reader issue. The empty array never even reaches react/useState only the '1' does.

thheller12:06:38

maybe helix does some stuff in a macro. I don't really know how hot-reload works with hooks. I would assume useState is always lost, just like local state is normally lost. given that component type changes it should go through a full unmount/mount cycle

Alex J Henderson12:06:53

the weird thing is it seems to be related to the tagged literal. If I write

(defnc test-component []
  (let [a #js []
        [test-state set-test-state] (react/useState (do a 1))]
    ($ "button" {:on-click #(set-test-state inc)} (str "test1: " test-state))))
it works fine

Alex J Henderson12:06:14

And this works fine also

(defnc test-component []
  (let [[test-state set-test-state] (react/useState (do (array) 1))]
    ($ "button" {:on-click #(set-test-state inc)} (str "test1: " test-state))))
that's what makes me think it's either clojurescript or shadow

thheller12:06:17

really need to ask in the #helix channel. I don't have a clue what defnc does internally

Alex J Henderson12:06:33

ok thanks very much ๐Ÿ™‚

thheller12:06:19

just a glance at the macro code makes me pretty certain that it is the macro

thheller12:06:27

not shadow or cljs

Alex J Henderson12:06:34

ah brilliant, that's very helpful, I guess it's analysing the code for calls to react hooks and something about the tagged literal is tripping it up, cheers

thheller11:06:48

but yeah not a shadow-cljs issue. that is either helix or react, don't know

Aron11:06:00

helik ๐Ÿ˜„