Vite linked React library: fix Invalid hook call
Separate a library bundling mistake from linked-package resolution, keep React external, and prove that the component and renderer use the same React.
TL;DR#
If a Vite-built component library works alone but throws Invalid hook call after linking it into an application, check React module identity, not just version numbers. Keeping React external prevents it being embedded in the library bundle. A linked library can nevertheless resolve its own development installation while the application renderer uses another copy.
For a Vite consumer, add React and React DOM to resolve.dedupe in the consuming application's configuration. Keep the library's React peer declarations and externalisation too; they solve different parts of the problem. This diagnosis assumes the component obeys the Rules of Hooks and the React/renderer versions are compatible.
The Stack Overflow report already externalised React and React DOM. Recommending that same setting again would not change its build. It also used npm link, which is specifically identified in React's duplicate-copy guidance as a way a bundler can encounter application and library copies.
Why this happens with matching React versions#
An isolated reproduction on 28 October 2026 used Vite 8.3.5, React and React DOM 19.2.4, and Node 26.7.0 on Windows. The library had React peer metadata, an external React import in its built output, and its own physical development copy of React. The consumer had a separate copy of the same version and linked the library through a filesystem junction.
Vite built a client-resolution test bundle. That bundle was executed with ReactDOMServer under Node to exercise a component's hook; this was not a browser test or a Vite SSR-mode build. Before deduplication, the identity comparison was false and rendering produced:
SAME_REACT=falseInvalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:The exception then named the hook used by the component:
TypeError: Cannot read properties of null (reading 'useContext')The component called the hook at the top level during rendering. Matching versions and correct hook placement were therefore negative controls: neither prevented the error while the React objects differed.
Fix the consumer's resolution first#
1. Confirm which application is running the library#
Run the dependency-tree check in the consuming application, then in the library directory:
npm ls react react-domLook for unexpected nested installations and incompatible peer ranges. Matching version strings do not prove one module instance: the reproduction deliberately copied the same release into two locations. A library's local development dependencies are especially relevant when the consumer follows a link back into that library's real directory.
Also identify the consumer's bundler. Building the library with Vite does not imply that its consumer uses Vite; the original report even contains a webpack-generated identifier. The following Vite option affects a Vite consumer. For another bundler, use its own module-resolution configuration or make the linked library resolve the application's React copy as described by React's official linking guidance.
2. Dedupe in the application that imports the package#
Merge this into the consumer's existing Vite configuration, keeping its plugins and other settings:
import { defineConfig } from 'vite';
export default defineConfig({
resolve: {
dedupe: ['react', 'react-dom'],
},
});Vite documents resolve.dedupe for duplicated dependencies caused by hoisting or linked packages. It resolves listed packages from the project root. Restart the consumer after changing its configuration, then test the linked component again. Adding this only to the library's configuration does not transfer the setting to another project's build.
In the reproduction, this was the only configuration change between failing and successful consumer bundles. The library output, peer declarations, installed versions and component code stayed the same. The resulting output was:
SAME_REACT=true
<span>shared</span>The failure exited with an error; the corrected render completed successfully. The library already externalised React before either consumer build, so the test also shows why externalisation alone was insufficient.
3. Keep React external in the library distribution#
Inspect the library output separately. The Vite library-mode guide recommends externalising dependencies that should not be bundled into a library, explicitly including React. For the Vite version tested here, a minimal ES-module library build can be configured as follows:
import { defineConfig } from 'vite';
import { fileURLToPath } from 'node:url';
export default defineConfig({
build: {
lib: {
entry: fileURLToPath(new URL('./src/index.js', import.meta.url)),
formats: ['es'],
fileName: 'index',
},
rolldownOptions: {
external: (id) => /^(react|react-dom)(\/|$)/.test(id),
},
},
});This example expects a JavaScript entry at the stated path; retain the appropriate entry and React/TypeScript plugins for your own library. The predicate also matches package subpaths, avoiding a narrow exact-name list if the output imports React's JSX runtime. Verify the resulting files instead of assuming the configuration reached the entry that your package exports.
The source question uses the older build.rollupOptions spelling. Current Vite build options document build.rolldownOptions; use the option appropriate to the installed release. Do not copy an old configuration block blindly into an unrelated current setup.
4. Declare the React versions the host must supply#
Use peerDependencies to describe the React compatibility contract of the published library. npm documents peer dependencies as compatibility with a host package. Set the range to releases you actually support and test; copying a broad range does not establish compatibility.
Development dependencies may still provide React for the library's own tests and examples. That is why moving a declaration into peerDependencies is useful package design but not proof that a linked development tree resolves one copy. The failing fixture already had peers. Check the packaged distribution and the consuming resolver as well as the manifest.
Verify the module, not only a successful library build#
During diagnosis, temporarily expose the library's React value from a diagnostic entry:
import React from 'react';
export const libraryReact = React;In the consumer, compare that value with its own React import and render a small library component that uses a hook. The reproduction used this comparison plus a context-reading component; both were needed. A successful library build alone never executed the consumer's renderer and therefore could not reveal the runtime mismatch. Remove the diagnostic export when finished.
If exporting diagnostics alongside a component triggers the separate Fast Refresh export warning, keep the diagnostic module separate. That lint warning concerns export boundaries; disabling it does not merge React instances.
There are limits to this remedy. Vite documents a deduplication caveat for ESM SSR build outputs. The client-resolution reproduction above does not establish a fix for every SSR externalisation setup. Likewise, multiple independent React applications can exist on a page; the problem is a component and its renderer using different React exports within the same rendering relationship.
If the error changes to a linked module not providing an export, inspect the library's ES-module output and package entry rather than continuing to tune hooks. If Vite instead cannot resolve a dependency's entry file, follow the package-entry diagnosis. Those failures occur during module loading; this failure occurs when the renderer calls a component whose hook comes from another React instance.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
Vite build Unexpected token in node_modules: fix define
Why vite build fails on react-router-dom while vite dev works, which define line triggers it, and a polyfill setup that builds, reproduced on Vite 2.9.12.
Vite failed to scan dependencies: unwanted HTML entries
Reproduce a dependency scan that includes gitignored documentation, then narrow the entry list without hiding syntax errors in the application itself.
Expected identifier but found "import" (define name) in Vite
Why a Vite 6 project stops before the dev server starts, why reinstalling from the same lockfile brings the error back, and the one-line update that clears it.


