Troubleshoot import issues related to third-party dependencies (specifically `react-hook-form`) within the Vite library framework

Currently, I am in the process of creating a custom UI library using Vite that acts as a simple wrapper for ShadCN Uis components. While everything works smoothly when utilizing the library within pages router components in a couple of NextJS apps, import errors start to surface when integrating it into app router components.

The root cause of these errors lies in the imports (and re-exports) from a third-party dependency called react-hook-form within the library codebase. Specifically, I encounter the following issues when executing npm run build:

./node_modules/shadcn-ui-lib/dist/index6.js Attempted import error: 'FormProvider' is not exported from 'react-hook-form' (imported as 'u').

Import trace for requested module: ./node_modules/shadcn-ui-lib/dist/index6.js ./node_modules/shadcn-ui-lib/dist/index.js ./src/app/rsc/page.tsx

./node_modules/shadcn-ui-lib/dist/index6.js Attempted import error: 'Controller' is not exported from 'react-hook-form' (imported as 'p')

Here is an excerpt from index6.js in the node_modules directory of the consuming app referenced in the error:

import { Label as f } from "./index5.js";
import { Slot as F } from "@radix-ui/react-slot";
import * as e from "react";
import { FormProvider as u, Controller as p, useFormContext as x } from "react-hook-form";
import { useForm as M } from "react-hook-form";
const R = u;

// ...

export { R as Form, C as FormControl, w as FormDescription, $ as FormField, I as FormItem, g as FormLabel, E as FormMessage, M as useForm, a as useFormField };

A few perplexing observations have left me puzzled:

  • The error solely occurs in app router components, functioning flawlessly in pages router components.
  • The successful import of Slot from @radix-ui/react-slot, despite treating dependencies like @radix-ui/react-slot and react-hook-form identically, raises questions.
  • Upon inspecting the node_modules folder in the consuming app, react-hook-form installation and the exportation of FormProvider match expectations.
  • Errors persist even without directly importing anything from react-hook-form in the consuming app but arise as soon as any component from the library is utilized.

Implementation Details

library/vite.config.ts

// @ts-ignore Not sure how to solve this but not worth the time to figure it out...
import * as packageJson from './package.json';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';

export default defineConfig({
  plugins: [
    react({
      // Not really required but seems to make the bundle a bit smaller
      jsxRuntime: 'classic',
    }),
    dts({
      include: ['src/**/*'],
    }),
  ],
  build: {
    lib: {
      entry: resolve(__dirname, 'src', 'index.ts'),
      formats: ['es'],
      fileName: 'index',
    },
    rollupOptions: {
      external: [...Object.keys(packageJson.peerDependencies || {}), ...Object.keys(packageJson.dependencies)],
      output: { preserveModules: true, exports: 'named' },
    },

    target: 'esnext',
    sourcemap: true,
  },
});

library/package.json

{
  "name": "shadcn-ui-lib",
  // ...
}
</p>

library/src/components/form.tsx The Source which holds the troublesome imports & re-expors from react-hook-form):

'use client';

import { cn } from '../lib/css.utils.js';
import { Label } from './label.js';
import type * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';
import { Controller, type ControllerProps, type FieldError, type FieldPath, type FieldValues, FormProvider, useForm, useFormContext } from 'react-hook-form';

const Form = FormProvider;

// Definitions of form components, hooks etc. (`FormItem`, `FormField`, `useFormField`, ...)

export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField, useForm };

consuming-app/src/app/rsc/page.tsx

import { Card } from 'shadcn-ui-lib';

export default function Home() {
  return (
    <main>
      <Card>RSC test page</Card>
    </main>
  );
}

Guesswork

I specifically restricted my library to "ESM only" due to complications encountered when attempting to export both CJS & ESM concurrently causing unresolved issues in the consuming apps. Perhaps this restriction contributes to the problem?

In comparing

@radix-ui/react-slot/package.json
(functioning correctly) with react-hook-form/package.json (resulting in issues), I noticed that react-slot lacks cjs files while react-hook-form offers both cjs and esm files.

Could it be that my consuming apps (or the library itself) are trying to import the cjs files from react-hook-form instead of the esm files? If so, how can this be rectified?

Reproduction

To reproduce the issue, you can download a minimal example of the library and a test-consuming app here:

Run npm i && npm run dev or npm run build in the consuming app to replicate the issues.

Conclusion

Being relatively new to library authoring, the intricacies of ESM & CJS elicit confusion, especially regarding their implications. The source of the issue remains uncertain, whether originating from my library, the consuming apps, or the third-party dependencies.

Your insights and guidance on this matter would be deeply appreciated, and I am willing to provide additional information if necessary.

Update

Edit actions performed directly on react-hook-form/package.json within my consuming app yielded interesting results:

Eliminating the react-server condition from the conditional exports appeared to resolve the issue (at least apparent through the absence of build errors...):

"exports": {
  "./package.json": "./package.json",
  ".": {
    "types": "./dist/index.d.ts",
    "react-server": "./dist/react-server.esm.mjs", // <-- Remove this
    "import": "./dist/index.esm.mjs",
    "require": "./dist/index.cjs.js"
  }
},

Hence, it appears that the errors may not stem from ESM/CJS distinctions but rather the presence of this react-server export.

Question

This development prompts a crucial question about possible adjustments in either my consuming app or library to address this anomaly?

For your reference, this issue does not manifest when directly importing react-hook-form in a Next JS app router but only surfaces when imported indirectly via my library.

Answer №1

To solve the issue, re-declare the useForm function imported from react-hook-form before re-exporting it from the library file form.tsx:

'use client';

// ...

import {
  // ...
  useForm as useFormImport,
} from 'react-hook-form';

// ...

const useForm = useFormImport;

export { useForm, /* ... */ };

What's the reasoning behind this solution?

The original export of useForm from react-hook-form lacks a use client directive. By re-declaring it before exporting, the use client directive from form.tsx gets properly attached to the re-declared useForm.

Why did this pose a problem without explicitly importing anything from form.tsx in my server component?

Since all components are exported from one barrel file (index.ts) in my library, even importing just one component like Card pulls in all exports from that barrel file due to how React processes imports.

Check out this answer by @morganney for more insights on this topic that helped me reach this conclusion.

Answer №2

Alexander's response is accurate, but lacks clarity.

Your app is importing the Card component like shown in this link:

import { Card } from 'shadcn-ui-lib';

As per the defined libraries' exports, this import will also try to load exports from ./components/form.js which includes react-hook-form.

Next.js 14 uses React Server Components by default and you need to opt-in for Client Components. Client components are necessary when your page relies on Browser APIs, such as event handlers and react-hook-form. So, for a Next.js page using react-hook-form, add 'use client' at the top of the file.

To resolve this issue, include the following line in src/app/rsc/page.tsx within your app:

'use client';

The directive was mistakenly added in the wrong place.

Additional Points:

  • Barrel files can create confusion in imports and affect tree shaking efficiency, leading to larger bundles. Consider restructuring your library's exports to allow importing individual components.
  • An RFC for defining React Server Component exports utilizes a react-server condition to define a module's export when consumed on the server. This approach is used in React Hook Form's package.json file, explaining the possible suggestions for server exports in error messages.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Master the art of using express and jade to loop through data and generate dynamic

Hey there! I have a question regarding node.js. When working with express, we typically have multiple files such as app.js, index.jade, index.js, and server.js where most of the server logic resides. Let's say we have two objects defined in server.js ...

When trying to access the same path, useEffect does not trigger

I integrated the API to execute when the screen is loaded using useEffect in Next.js v10. The code implementation is straightforward: ... const fetchAPI = async () => { try { await axios.get({ .... }) } catch (e) { console.error(e) } } R ...

The data is not visible on the table

I am experiencing some challenges with my MEAN stack website development. I need to display data on a table. routing node.js var Property = mongoose.model('Property'); var Schema = mongoose.Schema; var ObjectId = ...

"Utilizing the power of Express with Node.js, Socket.io,

In my current setup, both the back and front end are running on a LAMP environment accessible via 192.168.80.213/backend and 192.168.80.213/frontend. I am working on setting up a push notification server using node.js, socket.io, and the express framework ...

Ways to separate handleSubmit() functions in React Hooks Form to prevent them from intermingling when nested within each other

I recently integrated React Hook Form into my Next JS App for handling forms. In my setup, I have two form components named FormA and FormB. However, I encountered an issue where triggering the handleSubmit() function for FormB also triggered the handleSub ...

What is the reason behind the limitations in the C++ node API for Electron that prevent the initialization of array buffers with external data?

Recently, I was working on developing a NodeJs module for electron using C++ and the Node addon C++ API. My goal was to create an ArrayBuffer object that would contain the data of an image I had read using C++ iostream functions like ifstream::read(). To ...

Utilizing Express 4, create a fresh route within an app.use middleware

I'm currently working on an express nodejs app where I am attempting to implement "dynamic routes" within another route. Here is what I have: .... app.use('/test/:id', function(req,res,next) { app.use('/foo', sta ...

Interacting with various Node.js APIs from a React frontend

Hey there! I'm currently working on a project that utilizes Node.js (Typescript) for the backend and React with Express for the frontend. The backend consists of 3 docker containers, each assigned to different ports - 1 for Postgres, 2 for ServiceA, ...

It appears that the Node npm installation is deploying a distinct release compared to the one found on GitHub

On GitHub, I have noticed the desired version of generator-webapp; however, after performing an npm install, it appears to be providing me with an outdated version. I have observed that the version number in the packages.json file has not been modified du ...

What is the best way to utilize useClient in the code of my Next.js project?

I've been attempting to execute the code attached below, but I keep encountering an error stating that a component being imported requires useState and should only be used in a Client Component. However, none of its parent components are marked with " ...

Setting up JavaScript imports in Next.js may seem tricky at first, but with

Whenever I run the command npx create-next-app, a prompt appears asking me to specify an import alias. This question includes options such as ( * / ** ) that I find confusing. My preference is to use standard ES6 import statements, like this: import Nav f ...

Troubleshoot Node.js server-side rendering in Visual Studio Code

Debugging an SSR React application on the server side has been a major struggle for me. Our team is working on developing a new app from scratch, and with the project being so large, debugging the code is crucial. Here's the webpack configuration for ...

Can native types in JavaScript have getters set on them?

I've been attempting to create a getter for a built-in String object in JavaScript but I can't seem to make it function properly. Is this actually doable? var text = "bar"; text.__defineGetter__("length", function() { return 3; }); (I need th ...

Is there an easy method for sorting through JSON data with various criteria?

After receiving a response from my web service containing an array of objects, I am looking to filter the data based on various combinations of values. What is the most efficient way to do this without impacting the performance of my web service? Data: Th ...

Heroku - Node.js Application Issue

After successfully deploying the app using github, I encountered an issue when trying to access the website: Application Error An error occurred within the application preventing your page from being served. Please try again after a few moments. If y ...

Avoid encountering a mandatory field error when utilizing the save() function

I am currently enrolled in a course that is outdated, and unfortunately, there is no one available to answer my questions. I'm hoping the information provided will suffice. I have a concern about not receiving a required field error when using the sa ...

NodeJS throws an UnhandledPromiseRejectionWarning when I handle an error

I am looking to address the issue that arises when a user deliberately enters an incorrect objectId. However, I encountered an error when compressing the error into a function called check objectID: UnhandledPromiseRejectionWarning: Error: INVALID_ID Here ...

The React App causing the File Explorer in Windows to completely freeze up

After using the npm command to create a React app, my laptop's fan suddenly became louder and I encountered issues with File Explorer. Opening folders became unresponsive and it kept loading files indefinitely. This has greatly impacted my work produc ...

Issue encountered with Ionic and ssh2: process.binding is not supported

I am currently delving into the world of Ionic and experimenting with creating a basic application that utilizes SSH2 to establish an ssh connection between the app and a server. Here is a breakdown of the steps I took to encounter the issue: Steps to Rep ...

Launching a Node.js Express application on Heroku

I'm facing an issue while trying to deploy my app on Heroku, as I keep encountering the following error: 2022-08-11T12:49:12.131468+00:00 app[web.1]: Error: connect ECONNREFUSED 127.0.0.1:3306 2022-08-11T12:49:12.131469+00:00 app[web.1]: at TCPConnect ...