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 activeendDisplay
- takes in a JSX component to display when the data being fetched is exhausted and the loader is in the viewportonEnd
- takes in a function to run whenever the loader is in viewporthasMore
- 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
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.