Search code examples
javascriptreactjsreact-hooksuse-statemicrobundle

Unable to render useState, useEffect hooks in custom component package using microbundle & React


Hi I am trying to create a sample library using React 18.2.0 & Microbundle and Although library built successfully, but when it is consumed in the client app I'm getting the below error in console log:

Error

Library source code

Below is my library code.

App.js

import './App.css';
import Dropdown from "./components/Dropdown";

function App() {
   
  let dropdown_data = ['Item 1', 'Item 2', 'Item 3'];

    return (
      <div className="dropdown">
         <Dropdown jsonData={dropdown_data} />
      </div>
  )
}

export default App;

src/components/Dropdown.js

import React from "react";
import {useEffect, useState} from 'react';

export const Dropdown = (props) => {
    const [dropdown, setDropdown] = useState([]);
    useEffect(() => {
        loadData();
    }, []);
    const loadData = () => {
        setDropdown(props.jsonData);
    }
    return (
        <div className="dropdown">
            <select> {
                dropdown.map((item, index) => (
                    <option key={index}>
                        {item}</option>
                ))
            } </select>
        </div>
    )

}

src/lib.package.js

export { Dropdown } from "./components/Dropdown.js";

package.json

{
  "name": "libtestone",
  "version": "0.1.0",
  "private": true,
  "main": "./dist/lib.umd.js",
    "module": "./dist/lib.module.js",
    "source": "src/lib.package.js",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.3.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "build:lib": "microbundle --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "microbundle": "^0.15.0"
  }
}

Client application source code

This is where I am consuming the package.

App.js

import './App.css';
import {Dropdown} from "libtestone";

function App() {
  return (
    <div>
      <Dropdown />
    </div>
  );
}

export default App;

Solution

  • libone

    • Your library (libone/) must not have a hard dependency on react; a peer dependency is enough. (If you'd like it to have a self-contained demo app, then things are slightly different and I might recommend Vite's library mode like I've recently done here instead of microbundle).
    • The library should not depend on react-scripts in any way; it doesn't need to.

    All in all, this package.json does the trick (the script being named prepare so the dist/ files get built at a correct time; naturally you could still use build:lib, but then your prepare script must call it with e.g. npm run build:lib):

    {
      "name": "libone",
      "version": "0.1.0",
      "private": true,
      "source": "lib/index.js",
      "main": "./dist/lib.umd.js",
      "module": "./dist/lib.module.js",
      "peerDependencies": {
        "react": "^18.2.0"
      },
      "scripts": {
        "prepare": "microbundle build --globals react=React,react-dom=ReactDOM --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react"
      },
      "devDependencies": {
        "microbundle": "^0.15.0"
      }
    }
    

    userone

    • The client application userone can use react-scripts, vite, or whatever way you like to build a React app.
    • The client application's dependencies should contain React, ReactDOM, etc.

    With a layout that has libone/ and userone/ as siblings, using react-scripts, userone's package.json could be something like

    {
      "name": "userone",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "libone": "../libone",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-scripts": "5.0.1"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test"
      }
    }
    

    In addition, if you use npm link or yarn link for libone, it's imperative that libone's node_modules/ directory does not contain React; that will confuse Webpack.