I have a function that takes some arguments and renders an SVG. I want to dynamically import that svg based on the name passed to the function. It looks like this:
import React from 'react';
export default async ({name, size = 16, color = '#000'}) => {
const Icon = await import(/* webpackMode: "eager" */ `./icons/${name}.svg`);
return <Icon width={size} height={size} fill={color} />;
};
According to the webpack documentation for dynamic imports and the magic comment "eager":
"Generates no extra chunk. All modules are included in the current chunk and no additional network requests are made. A Promise is still returned but is already resolved. In contrast to a static import, the module isn't executed until the call to import() is made."
This is what my Icon is resolved to:
> Module
default: "static/media/antenna.11b95602.svg"
__esModule: true
Symbol(Symbol.toStringTag): "Module"
Trying to render it the way my function is trying to gives me this error:
Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
I don't understand how to use this imported Module to render it as a component, or is it even possible this way?
Your rendering functions (for class components) and function components should not be async (because they must return DOMNode or null - in your case, they return a Promise). Instead, you could render them in the regular way, after that import the icon and use it in the next render. Try the following:
const Test = () => {
let [icon, setIcon] = useState('');
useEffect(async () => {
let importedIcon = await import('your_path');
setIcon(importedIcon.default);
}, []);
return <img alt='' src={ icon }/>;
};
I made a change based on answer https://github.com/facebook/create-react-app/issues/5276#issuecomment-665628393
export const Icon: FC<IconProps> = ({ name, ...rest }): JSX.Element | null => {
const ImportedIconRef = useRef<FC<SVGProps<SVGSVGElement>> | any>();
const [loading, setLoading] = React.useState(false);
useEffect((): void => {
setLoading(true);
const importIcon = async (): Promise<void> => {
try {
// Changing this line works fine to me
ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./${name}.svg`)).default;
} catch (err) {
throw err;
} finally {
setLoading(false);
}
};
importIcon();
}, [name]);
if (!loading && ImportedIconRef.current) {
const { current: ImportedIcon } = ImportedIconRef;
return <ImportedIcon {...rest} />;
}
return null;
};
One solution to load the svg dynamically could be to load it inside an img using require, example:
<img src={require(`../assets/${logoNameVariable}`)?.default} />