phosphor-icons / homepage

The homepage of Phosphor Icons, a flexible icon family for everyone
https://phosphoricons.com
MIT License
3.89k stars 93 forks source link

Dynamically render icons by a string name #481

Open x10geeky opened 2 months ago

x10geeky commented 2 months ago

How can I dynamically render icons in React based on a string value?

rektdeckard commented 2 months ago

You've got a few choices. You can import the whole library and index into it by component name:

import * as P from "@phosphor-icons/react";

function App() {
  const Avocado = P['Avocado'];
  return <Avocado weight="fill" color="darkolivegreen" />;
}

This has perf consequences, but I'd do this for something like an electron or react native app, where bundle size is not an issue and the simplicity is nicer.

Another way (for which the exact details depend on your build tools and specific environment) is with dynamic imports, using code-splitting and Suspense to lazily import icons as needed. This example works with React + Vite:

import { lazy, Suspense } from 'react';
import type { IconProps } from '@phosphor-icons/react';

// This is essentially a map of relative paths to dynamic import functions,
// but since we use named exports we need to transform them to default exports.
// Most bundlers don't support template strings in dynamic imports, so we essentially
// let it code-split the whole Phosphor lib and allow them to be loaded at runtime.
const importMap = Object.entries(import.meta.glob("../node_modules/@phosphor-icons/react/dist/ssr/*.mjs"))
  .reduce((acc, [path, fn]) => {
    acc[path] = (name) => fn().then((mod) => ({ default: mod[name] }));
    return acc;
  }, {});

function relativePathForIcon(name: string) {
  return `../node_modules/@phosphor-icons/react/dist/ssr/${name}.mjs`;
}

export function LazyIcon(props: IconProps & { name: string }) {
  const { name, ...iconProps } = props;
  const Icon = lazy(() => importMap[relativePathForIcon(name)](name));

  return (
    <Suspense fallback={null}>
      <Icon {...iconProps} />
    </Suspense>
  );
}

// THEN IN YOUR APP
function App() {
  return <LazyIcon name="Avocado" weight="fill" color="darkolivegreen" />;
}