How to Create And Publish A React Library to NPM

How to Create And Publish A React Library to NPM

ยท

6 min read

In this article, we will learn how easy it is to create a reusable react component library we can share using npm.

We will be building a simple react infinite-scroll library that is compatible with both react-typescript and plain javascript react applications.

Why create an NPM package

There are several reasons to create an npm package, some of which are:

  • Sharing solutions to problems you've faced, that don't have solutions available on npm yet
  • Building an SDK for a service, etc

Who this article is for

For curious minds who want to see how the software they use is made. For developers who wish to share their works with the community. And for those who are willing to learn and add to their knowledge.

What this article won't cover

We will not cover documentation, or write tests for this library. The article aims only to present ideas on how to create a component library for reactjs applications.

Things you must know

The writer assumes the reader has at least a little background knowledge of React. And also have basic knowledge of npm

Building our library

As mentioned earlier, in this tutorial, we will be building an infinite scroll library for reactJS. ๐Ÿ™ƒ Yeah, We should have a bit of fun, not just a hello world library.

To build the library we need a bundler to compile and bundle our library code into one reusable piece. For this function, we will be using parcel, a javascript bundler. We are using parcel since it is very user-friendly and requires zero configuration to work with. Just something simple and straightforward.

The Project Structure

We first start by creating our project folder. Then run npm init to initialize an npm project. This creates a package.json file with details of the project.

Next, install parcel as a development dependency - npm install parcel --save-dev. Then create a folder called src in the project directory. This is where the source code for the project will live aside from other files that will be used during development, testing, and documentation.

Modify the package.json file to look something like this:

{
  "name": "react-infinitely",
  "version": "0.0.0",
  "description": "A react infinite scroll library made with intersection observer",
  "source": "src/index.tsx",
  "main": "dist/main.js",
  "module": "dist/module.js",
  "types": "dist/types.d.ts",
  "scripts": {
    "test": "jest",
    "watch": "parcel watch",
    "build": "parcel build"
  },
  "repository": {
...

You should note the source property value is the main source file path, the main property value being the main file to be run when the program is used in production, in this case, we want the compiled source files to be placed in the dist folder. types property when present specifies that a .d.ts file i.e a type definition file be generated. This file allows the use of existing javascript code in TypeScript.

Writing the library

So far, we have set up all necessary files for the development of the library. The next step is to write the actual library code, to do this, we will have the library code written inside a folder called lib as seen in the github repository. A file called infiniteScroll.tsx will contain the code below:

import { ReactElement, useEffect, useRef, useState } from "react";

const OPTIONS = {
  root: null,
  rootMargin: "0px",
  threshold: 0.05
};

const EmptyDisplay = <div></div>;

const InfiniteScroll = ({
  loader,
  endDisplay,
  onEnd,
  hasMore,
  children
}: { loader: ReactElement, endDisplay: ReactElement, hasMore: Boolean, onEnd: () => void, children: ReactElement[] }) => {
  const loadingRef = useRef<HTMLDivElement>(null);
  const observer = useRef<IntersectionObserver>();
  observer.current = new IntersectionObserver(
    handleObserver,
    OPTIONS
  );
  const [isIntersecting, setIsIntersecting] = useState(false);

  function handleObserver(entries: IntersectionObserverEntry[]) {
    const [entry] = entries;
    if(!entry.isIntersecting) return setIsIntersecting(false);
    setIsIntersecting(true);
  }

  useEffect(() => {
    // Perform fetch operation if loader container is in view
    if(isIntersecting) {
      hasMore && onEnd(); // call onEnd method if more content exist
    };
  }, [isIntersecting]);

  useEffect(() => {
    // initialize observer
    window.addEventListener("DOMContentLoaded", () => {
      const { current } = loadingRef;
      observer.current?.observe(current as HTMLElement);
    });
    return () => {
      observer.current?.disconnect();
    }
  }, [loadingRef]);

  return (
    <div className="infinite-scroll__container">
      <div className="infinite-scroll__items">
        {children}
      </div>
      <div className="infinite-scroll__loader-container" ref={loadingRef}>
        {hasMore ? loader : endDisplay}
      </div>
    </div>
  );
}

InfiniteScroll.defaultProps = {
  endDisplay: EmptyDisplay,
  loader: EmptyDisplay
}

export default InfiniteScroll;

Code Details

In the code above, a react component called InfiniteScroll was defined. And has props;

  • loader - receives a JSX to display when the loader is active
  • endDisplay - takes in a JSX component to display when the data being fetched is exhausted and the loader is in the viewport
  • onEnd - takes in a function to run whenever the loader is in viewport
  • hasMore - a boolean to tell if the component has more data to load

Then an intersection observer is used to check if the loader container is visible, then displays the loader if the component has more data to load and calls the onEnd function to load more data, else it shows the endDisplay component.

Exporting our library

After writing the necessary code in the relevant files, We will export the file Infinite scroll component like so:

import InfiniteScroll from "./lib/InfiniteScroll";
export default InfiniteScroll;

Obviously, from the above code, we import the InfiniteScroll component into the index.ts file and export it from there.

Testing our library

To test our library, we don't have to publish to npm and download to a project to test. This can be tedious, not professional, and quite overwhelming. Well, thanks to some of npm's handy functions, we have some tools at our disposal to make this process more manageable and uninvolved.

The concept of the NPM pack command

When developing an npm library you have to confirm it actually works before publishing it for users. Npm provides some tools for this, for example, the npm link command which creates a symlink, it simply means you connect your parent application to a module you have locally on your system. Also, we have the npm pack command available to us.

What it does is create a .tgz file exactly the way it would be when published to npm. And thanks to npm you can install a .tgz file as an npm package. To do this we use the npm install command while specifying the path to the .tgz library, see the example below;

# npm install <local_path_to_tgz_file>
npm install /home/spiff/react-apps/react-infinitely/react-infinitely-0.0.0.tgz

This command will install the library as an npm package wherever the command is run. You can inspect the dependencies section of the project to see the path. Something like this

"dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-infinitely": "file:../../../react-apps/react-infinitely/react-infinitely-0.0.0.tgz"
  }

Publishing to npm

The next step is to publish the library to npm. To do this, follow the steps below:

npm login
npm publish

Screenshot from 2022-06-20 16-07-47.png

And that's it. Now, we can check npm's repository and find the library.

Closing

In this article we saw a simple way to create a react library packaged for reuse, how to test the library locally before publishing to npm, and finally publish the package to npm. To try this out, you can create something entirely different and publish your react components to npm. One should also note some naming issues while uploading a package to npm. If you have any questions, please feel free to ask in the comment section.

References

Did you find this article valuable?

Support SpiffGreen's Blog by becoming a sponsor. Any amount is appreciated!

ย