DEV Community

Cover image for The right way to use SVG icons with React
Konstantin
Konstantin

Posted on

The right way to use SVG icons with React

Hey, folks! I think you’ve read a lot of topics on how to use SVG in React.
The most popular approach is to use @svgr/webpack that allows you to import SVG as ReactComponent. If you create projects using awesome
create-react-app you already use this package.

Usually you do it this way, right?

import React from 'react';
import { ReactComponent as Icon } from 'path/to/icon.svg';
function Icon() {
    return <Icon />;
}

It works perfectly, BUT what if you want to create re-usable components to render your icons.

Let’s say, designer provides you a set of 50+ svg icons, how would you deal with them?

Many tutorials suggest to create components for each icon — it is straightforward, but tiresome way. Not to mention it produces a lot of boilerplate code.😖

If you have curiosity how to avoid useless boilerplating — welcome to my post.

I’ll present you a **simple, but **effective* way to manage SVG icons in your React App.*


TL;DR

If you are too impatient to get the answer, it is okay.
All you need to start using this elegant solution is a code snippet provided below.

import React, { useEffect, useState } from "react";

function Icon(props) {
  const { name, ...otherProps } = props;

  /* Use state hook to store icon module value */
  const [iconModule, setIconModule] = useState(null);

  useEffect(() => {
    /* Use dynamic import to get corresponding icon as a module */
    import(`./icons/${name}.svg`)
      .then((module) => {
        /* Persist data in state */
        setIconModule(module);
      })
      .catch((error) => {
        /* Do not forget to handle errors */
        console.error(`Icon with name: ${name} not found!`);
      });
  }, [ name /* update on name change */ ]);

  const renderIcon = () => {
    if (!iconModule) return null;

    /* Equal to: import { ReactComponent as Icon } from "./path/to/icon.svg" */
    const Component = iconModule.ReactComponent;

    return <Component {...otherProps} />;
  };

  return <>{renderIcon()}</>;
}
export default Icon;

Or you can play with it in codesandbox.io/s/how-to-use-svg-icon-sets-in-react:


How it works

Let’s dive into this approach together and go through it line by line.
It is simpler than it looks.
First, we create a new functional component that takes one required name prop.

function Icon({ name, ...otherProps }) {
    // implementation
}

Next, we use useState hook to store icon component value with null as initial value:

const [iconModule, setIconModule] = useState(null);

That’s where the magic happens✨…
We use dynamic expression in import to get icon depending on provided name. import returns Promise which resolves with module on success or rejects Erorr if icon not found.

Here we work with webpack bundling.

import(./icons/${name}.svg) will cause every .svg file in the ./icons directory to be bundled into the new chunk. At run time, when the variable name has been computed, any file like star.svg will be available for consumption. You can read more about this here.

import(`./icons/${name}.svg`).then((module) => {
    /* Persist data in state */
    setIconModule(module);
}).catch((error) => {
    /* Do not forget to handle errors */
    console.error(`Icon with name: ${name} not found!`);
});

Finally, we should render our icon component if it is successfully imported. Webpack applies @svgr/webpack loader
on dynamic import: import(./icons/${name}.svg)
same way it does for
static one: import Icon from “./path/to/icon.svg”

const renderIcon = () => {
    if (!iconModule) return null;
    /**
    * Equal to:
    * import { ReactComponent as Icon } from "./path/to/icon.svg";
    */
    const Component = iconModule.ReactComponent;
    return <Component {...otherProps} />;
};

That’s it guys 🎉.
Hope you enjoyed article and going to apply new knowledge for your next app. Feel free to comment and discuss it!

Top comments (3)

Collapse
 
kamesdev profile image
kamesdev

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of Icon.

Collapse
 
mal4ik profile image
Stepan Tsukanov • Edited

i think you are using next... i faced the same Error.
i installed package svgr react-svgr.com/docs/next/ by instruction and then i did this (it is different ^ but same idea to use svg file name as prop)

import type { FC, SVGProps } from 'react'
import dynamic from 'next/dynamic'

import clsx from 'clsx'

interface IIconProps {
  readonly name: string
  readonly className?: string
  readonly fill?: string
  readonly height?: number
  readonly widht?: number
  readonly onClick?: () => void
}

export default function Icon(props: IIconProps) {
  const { name, className, ...otherProps } = props

  const CustomIcon = dynamic(() => import(`@/assets/svg/${name}.svg`)) as FC<SVGProps<SVGSVGElement>>
``
  return (
    <div className={clsx('d-flex position-relative', className)}>
      <CustomIcon {...otherProps} />
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
joaodos3v profile image
João Vitor Veronese Vieira

iconModule always remains null to me. Do you have any idea what it might be? Thanks