How can I export a function within a React functional component to build a library?

31 Views Asked by At

I am in the process of creating a React component library, and I have a function within one of my library components that I would like to export. The function, addParticle, is intended to allow users of the library to dynamically add particles to a container by passing props. Here's the code snippet of my component:

import React, { useState } from "react";
import Particle from "../Particle";
import "./ParticleContainer.css";

export interface AddParticleProps {
  src: string;
  height: string;
  width: string;
  animationDuration: number;
}

interface ParticleContainerProps {
  height?: string;
  width?: string;
  position?: "relative" | "absolute";
}

function ParticleContainer({
  height = "40px",
  width = "40px",
  position,
}: ParticleContainerProps) {
  const [particleDetails, setParticleDetails] = useState<AddParticleProps[]>(
    []
  );

  const addParticle = ({
    src,
    height,
    width,
    animationDuration,
  }: AddParticleProps) => {
    setParticleDetails([
      ...particleDetails,
      { src, height, width, animationDuration },
    ]);
  };

  return (
    <div className="particlesContainer" style={{ height, width, position }}>
      {particleDetails.map((props, index) => {
        return <Particle key={index} id={index} {...props} />;
      })}
    </div>
  );
}

export { addParticle }; // How to properly export this function?

export default ParticleContainer;

1

There are 1 best solutions below

0
Robin Zigmond On BEST ANSWER

You can't do this directly. addParticle is a function within your ParticleContainer component, inaccessible from the outside scope - and you can't move it outside because it references the setParticleDetails function which is necessary internal to the component (as that is returned from useState which, like all React hooks, can only be called from inside a function component).

What you can do though, and which is perfectly normal and expected for a React component library, is to export a custom hook to do this. There are a few ways to design the API, depending on what you want consumers to be able to do with your library, but as a starting point based on what you've already shared, I would guess you want to return an object (an array would also work, but I've gone with an object below) containing both the UI of all the rendered Particle component, and the addParticle function:

function useParticles() {
  const [particleDetails, setParticleDetails] = useState<AddParticleProps[]>(
    []
  );

  const addParticle = ({
    src,
    height,
    width,
    animationDuration,
  }: AddParticleProps) => {
    setParticleDetails([
      ...particleDetails,
      { src, height, width, animationDuration },
    ]);
  };

  const particles = particleDetails.map((props, index) => {
    return <Particle key={index} id={index} {...props} />;
  });

  return { particles, addParticles };
};

Once you have this, consumers can easily recreate your proposed ParticleContainer component like this:

function ParticleContainer({
  height = "40px",
  width = "40px",
  position,
}: ParticleContainerProps) {

  const { particles, addParticle } = useParticles();

  return (
    <div className="particlesContainer" style={{ height, width, position }}>
      {particles}
    </div>
  );
}

where the addParticles function is not currently used, but can easily be used however the consumer wants to within the component (eg pass it as an onclick handler to some button in a child component).

This also decouples the logic of rendering and adding "particles" from the styling of the container component (the width, height and position) - the ParticleContainer, or whatever alternative a consumer wants to use for a container, contains that information, and the useParticles hook is looking after the logic of adding the particles themselves.