npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nice-toolkit

v3.2.0

Published

Link local packages while preventing duplicate React/styled-components. Essential for the Nice ecosystem.

Readme

nice-toolkit

A utility to streamline the npm linking process for React component libraries when working locally, automatically removing conflicting peer dependencies that cause "Invalid hook call" errors, duplicate React instances, and styled-components context issues.

CLI binaries: nice-toolkit (long-form) and ntk (short alias).

The Story: Why This Tool Exists

You're developing a React component library. You run npm link to test it locally in your app. Everything seems fine until you refresh the page and see:

Error: Invalid hook call. Hooks can only be called inside the body of a function component.

You check your code - the hooks are definitely inside a component. You search Stack Overflow. You clear caches. You reinstall node_modules. Nothing works.

The real problem? When you npm link your component library, it brings along its own node_modules folder containing its own copy of React. Now you have two separate React instances running in your app - one from your app's node_modules/react and one from your linked library's node_modules/react. React detects this and throws the "Invalid hook call" error because components from one React instance are trying to use hooks managed by a different React instance.

This isn't just a React problem - styled-components, @emotion/react, and similar libraries have the same issue because they maintain global state and use React Context internally. Multiple instances = broken context = cryptic errors.

The "solution" everyone suggests? Manually delete React from your linked package's node_modules, link again, remember to do this every time you reinstall, and hope you don't forget. That's tedious and error-prone.

nice-toolkit automates this entire process. It removes the conflicting packages from your linked library's node_modules, ensures React and friends are listed as peerDependencies, and handles the linking - all in one command.

Why Symlinks Cause Duplicate React Instances

When you use npm link, here's what actually happens:

your-app/
├── node_modules/
│   ├── [email protected]                    ← Your app's React
│   └── my-component → /Users/you/my-component/  ← Symlink!

/Users/you/my-component/
└── node_modules/
    └── [email protected]                    ← DUPLICATE React instance!

When my-component does import React from 'react', Node.js resolves it relative to the symlink's target location (/Users/you/my-component/), not your app's location. It finds /Users/you/my-component/node_modules/react first - a completely separate React instance from the one in your app.

Why doesn't this happen with regular (non-linked) dependencies? Because npm hoists them:

your-app/
├── node_modules/
│   ├── [email protected]                    ← ONE shared React instance
│   ├── [email protected]/             ← Regular dependency (NOT a symlink)
│   │   └── (no node_modules here - uses hoisted React above)
│   └── [email protected]/
│       └── (no node_modules here - uses hoisted React above)

All packages resolve to the same React instance at the top level. With symlinks, this hoisting doesn't work because each symlinked package resolves modules from its own location.

Which Packages Need to Be Singletons?

Not all duplicate packages cause problems - only those that maintain global state or use object identity checks:

Must be singleton (nice-toolkit removes these):

  • react - maintains a global Fiber tree and hook state
  • react-dom - maintains a global DOM renderer
  • styled-components - maintains a global StyleSheet registry and uses React Context
  • @emotion/react - maintains a global emotion cache
  • scheduler - global task queue used by React
  • react-is - used for type checking by React internals

Can have duplicates (your component libraries):

  • nice-react-button - just exports functions/components
  • lodash - just pure utility functions
  • Most regular npm packages - they're stateless code

This is why you can have multiple versions of your own component libraries in node_modules without issues, but multiple React instances break everything.

Solution

nice-toolkit solves this by:

  1. Removing conflicting singleton packages - Deletes React, React DOM, styled-components, and their TypeScript definitions from the linked package's node_modules
  2. Enforcing peerDependencies - Ensures React and friends are listed as peerDependencies so they use your app's versions
  3. Simple CLI interface - One command handles everything automatically

Installation

Global Installation (Recommended)

npm install -g nice-toolkit

Local Installation

npm install nice-toolkit
npx nice-toolkit --help

Usage

Link a Component Package

# From your main project directory
nice-toolkit ../nice-react-button

# Or with absolute path
nice-toolkit /Users/username/Code/nice-react-button

Clean Only (Remove Conflicting Packages)

# Remove default packages only
nice-toolkit --clean-only

# Remove custom packages only
nice-toolkit --clean-only --exclude @mui/material,@mui/icons-material

# Add extra packages to default list
nice-toolkit --clean-only --add-exclude @emotion/react,@emotion/styled

Custom Package Exclusion

# Override default packages with custom list
nice-toolkit --exclude react,react-dom ../nice-react-button

# Add additional packages to the default exclusion list
nice-toolkit --add-exclude @emotion/react,@emotion/styled ../nice-react-button

# Combine both (removes only react, react-dom, and @mui packages)
nice-toolkit --exclude react,react-dom --add-exclude @mui/material ../nice-react-button

Help

nice-toolkit --help

What It Does

  1. Removes conflicting packages from your node_modules:

    • Default packages removed:
      • react
      • react-dom
      • styled-components
      • @types/react
      • @types/react-dom
    • Or your custom list of packages using --exclude
    • Or default packages plus your additions using --add-exclude
  2. Creates npm link in the target package directory

  3. Links the package to your current project

  4. Provides feedback on each step with clear success/error messages

Example Workflow

# In your main project (e.g., helpshelf-ui)
cd /path/to/helpshelf-ui

# Link your button component
nice-toolkit ../nice-react-button

# Now you can import and use the component
# Changes to nice-react-button will be reflected immediately

Advanced Usage Examples

Working with Monorepos

# Link a package from a monorepo workspace
nice-toolkit ../my-monorepo/packages/ui-components

# Force a specific package manager in a monorepo
nice-toolkit --manager pnpm ../my-monorepo/packages/ui-components

Development Workflow with Watch Mode

# 1. Link the package
nice-toolkit ../nice-react-button

# 2. In the linked package directory, start watch mode
cd ../nice-react-button
npm run dev  # or npm run build:watch

# 3. In your main project, start your dev server with symlink support
cd /path/to/your-app
NODE_OPTIONS=--preserve-symlinks npm start

# Now changes in nice-react-button will auto-rebuild and appear in your app!

Testing Multiple Linked Packages

# In your main project, link multiple dependencies
nice-toolkit ../nice-react-button
nice-toolkit ../nice-react-icon
nice-toolkit ../nice-react-flex

# All three packages are now linked and share the same React instance

Custom Exclusion Patterns

# Only remove React and React DOM (skip styled-components)
nice-toolkit --exclude react,react-dom ../my-component

# Remove default packages plus Material-UI
nice-toolkit --add-exclude @mui/material,@mui/icons-material ../my-component

# Combine with other options
nice-toolkit --manager yarn --add-exclude @emotion/react ../my-component --dry-run

Dry Run for Safety

# Preview what would happen without making changes
nice-toolkit --dry-run ../nice-react-button

# See what would be cleaned without linking
nice-toolkit --clean-only --dry-run ../nice-react-button

Skip Automatic peerDependencies Management

# Link without modifying the linked package's package.json
nice-toolkit --skip-peer-check ../nice-react-button

Unlinking

To unlink a package, simply change the package.json entry back to a version number:

# Manual method: Edit package.json
# Change: "my-package": "file:../my-package"
# To:     "my-package": "^1.0.0"

# Then reinstall
npm install

Or remove the dependency entirely and reinstall from npm:

npm uninstall my-package
npm install my-package

Troubleshooting

"Invalid hook call" Error

Problem: You're seeing errors like "Invalid hook call. Hooks can only be called inside the body of a function component."

Solution: This means multiple copies of React are running. Ensure you've removed React from the linked package:

# Re-run the link command to clean up
nice-toolkit ../my-component

# Or use clean-only to just remove conflicts
nice-toolkit --clean-only ../my-component

Module Not Found After Linking

Problem: After linking, your app can't find the linked module.

Solution: Make sure the linked package has been built:

cd ../my-component
npm run build
cd -
nice-toolkit ../my-component

Changes Not Appearing in Development

Problem: You modify the linked package but don't see changes in your app.

Solutions:

  1. Use a watch/dev script in the linked package:

    cd ../my-component
    npm run dev  # or build:watch
  2. For Create React App, use the --preserve-symlinks flag:

    NODE_OPTIONS=--preserve-symlinks npm start
  3. Restart your dev server after making changes if you don't have watch mode.

Styled-Components Context Issues

Problem: Errors about multiple instances of styled-components or "Cannot read property 'xxx' of undefined" in styled-components.

Solution: Ensure styled-components is removed from the linked package:

# Default behavior removes styled-components
nice-toolkit ../my-component

# Or explicitly add it
nice-toolkit --add-exclude styled-components ../my-component

TypeScript Declaration Conflicts

Problem: TypeScript errors about conflicting React type definitions.

Solution: The default configuration already removes @types/react and @types/react-dom. If you still have issues:

# Ensure types are removed
nice-toolkit --add-exclude @types/react,@types/react-dom ../my-component

# Check your linked package's package.json
# React types should be in devDependencies or peerDependencies, not dependencies

Package Manager Detection Issues

Problem: Wrong package manager is being used.

Solution: Force the correct package manager:

nice-toolkit --manager pnpm ../my-component
nice-toolkit --manager yarn ../my-component
nice-toolkit --manager npm ../my-component

Workspace/Monorepo Conflicts

Problem: Getting warnings about workspaces, or linking doesn't work in a monorepo.

Solution: In monorepos, consider using native workspace features instead:

// package.json in your app
{
  "dependencies": {
    "my-package": "workspace:*"  // pnpm/yarn workspaces
  }
}

Or use the file: protocol directly:

{
  "dependencies": {
    "my-package": "file:../packages/my-package"
  }
}

Permission Errors

Problem: Getting EACCES or permission denied errors.

Solution:

  1. Don't use sudo with npm link - it can cause permission issues
  2. Fix npm permissions: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally
  3. Use --dry-run to see what would happen first

Link Successful but Package Not Updated

Problem: The link command succeeds but your app still uses the old version.

Solution:

  1. Clear your bundler cache:

    # For Create React App
    rm -rf node_modules/.cache
    
    # For Next.js
    rm -rf .next
    
    # For Vite
    rm -rf node_modules/.vite
  2. Restart your development server

  3. Verify the link:

    ls -la node_modules/my-package
    # Should show it pointing to your local directory

Requirements

  • Node.js (v14 or higher recommended)
  • npm, yarn, or pnpm
  • The target package must have a valid package.json
  • You must run this from the project where you want to link the package

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT