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;