简体   繁体   中英

Can i import the SVG file as a react component with dynamic import() Expressions?

From this answer from stackoverflow, I got a solution on how to import svg as ReactComponent and change their color/width, etc.

But is it possible to do the same for dynamic import? My functional component:

import * as React from 'react';
import SvgIconComponent from './SvgIcon.Interface';

import {ReactComponent} from '*.svg';

const SvgIcon: React.FC<SvgIconComponent> =({width, color, name}) =>
{
    import(`../../assets/icons/${name}.svg`).then((Icon) => {
        return <Icon fill={color} width={width}/>
    });
};

export default SvgIcon;

In the current implementation I get errors:

TS2749: 'ReactComponent' refers to a value, but is being used as a type here. // .then((Icon as ReactComponent)

TS2604: JSX element type 'Icon' does not have any construct or call signatures. // .then(Icon)

I suggest you make use of process.env.PUBLIC_URL to render your svg. Have all your svg files inside public folder, let's say inside public/svgFiles .

you can then use it as <img src={process.env.PUBLIC_URL + "/svgFiles/" + file_name} alt="project" />

Since everything from the public folder is included in the final build , all your svg files will be taken care of, along with the path you use, since everything will be referred from public folder, path will not be changed after build.

I think it is a better way to use the React component to make the dynamic icons for the color and width.

I add my example code:

export const TwitterIcon = ({color, width}: Props) => (
    <svg
        width={width ? width : 24}
        height={width ? width : 24}
        viewBox="0 0 24 24"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
    >
        <path
            fill={color ? color : "#1f1f1f"}
            d="M8.0798 19.9998C9.55849 20.0493 11.0321 19.8018 12.4135 19.2721C13.795 18.7424 15.0562 17.9411 16.1227 16.9156C17.1892 15.8901 18.0393 14.6613 18.6228 13.3017C19.2063 11.9421 19.5113 10.4793 19.5198 8.9998C20.1974 8.16121 20.7006 7.19563 20.9998 6.1598C21.0221 6.07796 21.0203 5.99141 20.9946 5.91057C20.9689 5.82974 20.9203 5.75806 20.8548 5.70417C20.7893 5.65027 20.7096 5.61647 20.6253 5.60683C20.5411 5.59718 20.4558 5.6121 20.3798 5.6498C20.0253 5.82045 19.6262 5.87558 19.2386 5.80741C18.8511 5.73923 18.4948 5.5512 18.2198 5.2698C17.8687 4.88538 17.4441 4.57533 16.971 4.35803C16.498 4.14072 15.9861 4.02059 15.4657 4.00473C14.9454 3.98888 14.4272 4.07763 13.9417 4.26574C13.4563 4.45384 13.0136 4.73746 12.6398 5.0998C12.128 5.59546 11.7531 6.21509 11.5516 6.89847C11.3501 7.58186 11.3288 8.30575 11.4898 8.9998C8.1398 9.1998 5.8398 7.6098 3.9998 5.4298C3.94452 5.3672 3.87221 5.32205 3.7917 5.29987C3.71119 5.27769 3.62596 5.27943 3.54642 5.30488C3.46688 5.33033 3.39648 5.37839 3.3438 5.4432C3.29113 5.508 3.25846 5.58674 3.2498 5.6698C2.89927 7.61422 3.15213 9.61935 3.97442 11.4159C4.7967 13.2124 6.14904 14.7143 7.8498 15.7198C6.70943 17.0276 5.10801 17.8445 3.3798 17.9998C3.28721 18.0152 3.20174 18.0591 3.13535 18.1254C3.06896 18.1917 3.02497 18.2772 3.00954 18.3698C2.99411 18.4623 3.00801 18.5574 3.0493 18.6417C3.09059 18.726 3.15719 18.7952 3.2398 18.8398C4.74332 19.5912 6.39903 19.988 8.0798 19.9998Z"
        />
    </svg>
);

And if you want to make the icons library

interface IconProps {
   keyword: string; //make the clear type to make switch
   color: string;
   width: number;
}

const SvgIcon = ({keyword, color, width}: IconProps) => {
   // In this case you have to think about the switch and types in typescript.
    return (
       {
           'twitter': <TwitterIcon color={color} width={width}/>,
           'facebook': <FacebookIcon color={color} width={width}/>,
       }[keyword]
   )
}

//in component
<SvgIcon keyword='twitter' width={24} color='red'/>

If you have too many SVG icons, I don't have any solution for it on my hands right now. If I have any good idea with it. I will post here again.

From what I can see your issue is that you are importing the whole file and trying to render that as a component, you need to append the key / component identifier/const name to the require. For example if you use export defualt YourSVGComponent you can access that dynamically by doing the following;

import * as React from 'react';
import SvgIconComponent from './SvgIcon.Interface';

const SvgIcon: React.FC<SvgIconComponent> =({width, color, name}) =>
{
    const Icon = require(`../../assets/icons/${name}.svg`).default
    return <Icon fill={color} width={width}/>
};

export default SvgIcon;

There are a number of mistakes here.

'ReactComponent' refers to a value, but is being used as a type here.

The ReactComponent that you are seeing in the linked answer is actually a property of the .svg file when it is loaded by Create React App. It is a super neat feature which I am just learning about for the first time today!

In that example code

import { ReactComponent as Logo } from './logo.svg';

What they are doing is taking the ReactComponent property from the './logo.svg' import and renaming it to Logo .

Your import {ReactComponent} from '*.svg'; doesn't make much sense.

You get the "type as value" error when using ReactComponent as a type because it is not a type. The type assertion that you were attempting to do with .then((Icon as ReactComponent) should have been .then((Icon as React.ComponentType<SomePropsType>) .

JSX element type 'Icon' does not have any construct or call signatures.

Let's take a moment to see what we actually get from the import statement.

import(`../../assets/icons/${name}.svg`).then(console.log);

You should see an object with three properties: ReactComponent , default , and Url .

{
  ReactComponent: ƒ SvgLogo(),
  default: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0i...",
  Url: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0i..."
}

The default import from the svg file (which is also the Url property) is the data-url for the image. You can use that in an img src property.

The ReactComponent is the property that we are after. That's where Create React App converts the svg into a React component: (Note. the name "SvgLogo" depends on the name of the file).

Nothing was returned from render.

Nothing is returned from your SvgIcon function. The return statement that you have is the return for the then callback.

Dealing with an asynchronous import is a bit tricky, as you won't have a value on the first render. In that case we can return null (or you might want to return a sized placeholder).

Here I am using the element state to store the returned element. It starts as null and gets replaced with the <Icon/> after it has been loaded.

I am calling the import inside of a useEffect with dependencies on your props. Once the import resolves, we get the Icon from res.ReactComponent .

The inferred type for Icon is any , which might be fine for you. If you want an accurate type, you can use the messy as React.ComponentType<JSX.IntrinsicElements['svg']>; which says "this is a React component which takes all of the props of an <svg> element".

import * as React from "react";

interface SVGIconProps {
  width: string | number;
  color: string;
  name: string;
}

const SvgIcon: React.FC<SVGIconProps> = ({ width, color, name }) => {
  const [element, setElement] = React.useState<JSX.Element | null>(null);

  React.useEffect(() => {
    import(`./icons/${name}.svg`).then((res) => {
      const Icon = res.ReactComponent as React.ComponentType<JSX.IntrinsicElements['svg']>;
      setElement(<Icon fill={color} width={width} />);
    });
  }, [name, color, width]);

  return element;
};

export default SvgIcon;
import MyIcon from '../../assets/icons/${name}.svg'

<MyIcon />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM