create-react-app
create-react-app copied to clipboard
Dynamic import doesn't work with SVG imported as ReactComponents
Is this a bug report?
Yes
Did you try recovering your dependencies?
Yes
Which terms did you search for in User Guide?
SVG, ReactComponent, dynamic import
Environment
System:
OS: macOS 10.14
CPU: x64 Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz
Binaries:
Node: 10.11.0 - /usr/local/bin/node
Yarn: 1.7.0 - /usr/local/bin/yarn
npm: 6.4.1 - /usr/local/bin/npm
Browsers:
Chrome: 69.0.3497.100
Firefox: 61.0.2
Safari: 12.0
npmPackages:
react: ^16.5.2 => 16.5.2
react-dom: ^16.5.2 => 16.5.2
react-scripts: 2.0.3 => 2.0.3
npmGlobalPackages:
create-react-app: 2.0.2
Steps to Reproduce
- Try to import SVG as ReactComponet using import(). Example code below
Expected Behavior
SVG loads
Actual Behavior
Got following error in browser
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.
This happens because importing module doesn't contain ReactComponent property. Here's console.log output from demo
Module {default: "/static/media/logo.5d5d9eef.svg", __esModule: true, Symbol(Symbol.toStringTag): "Module"}
Reproducible Demo
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor (props) {
super(props)
this.logoComponent = 'div'
}
componentDidMount () {
import('./logo.svg').then((m) => {
console.log(m)
this.logoComponent = m.ReactComponent
this.forceUpdate()
})
}
render() {
const Logo = this.logoComponent
return (
<div>
<Logo />
</div>
)
}
}
export default App;
Workaround
- Create file with following content
import { ReactComponent as Logo } from './logo.svg'
export default Logo
- Use dynamic import as usual for this file
I tried to move babel-plugin-named-asset-import from webpack config to babel preset and put it before @babel/plugin-syntax-dynamic-import plugin, but it didn't help. Any thoughts how to solve the issue? I'm not very familiar with create-react-app internals, so give an idea and I'll take a look :)
I too have this issue and would like to know a solution!
I have the same issue. Is there a solution for this? Apart from the workaround, which is a little bit annoying because you need to create one component for each svg file.
Another workaround is to use https://github.com/tanem/react-svg
Bringing this over from https://github.com/facebook/react/issues/17804
Replicated differently here: https://github.com/neolefty/indirect-svg Packaged as an NPM module: https://www.npmjs.com/package/replicate-indirect-svg
Update: I tried the workaround with export default Logo but it didn't work through the NPM module.
Having the same issue. It seems to work here: https://codesandbox.io/s/react-dynamic-svg-import-448dn?file=/src/App.js:777-791
So it might just be the babel/webpack configuration that's messing things up.
@neolefty Did you have any success resolving your particular issue? Having the same problem here and not even the slightest idea on how to resolve it.
Had the same problem in a next.js project. I was not using dynamic imports but had the same problem as described by @neolefty here: https://github.com/facebook/react/issues/17804
Error message caused by imported packaged:
Warning: React.createElement: 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. in Logo
The NPM package/library had the following code causing the error:
import { ReactComponent as SVG } from './logo-blue.svg';
The accepted answer in this post solved the problem: https://stackoverflow.com/questions/60300372/in-nextjs-how-to-build-when-referencing-external-npm-package-that-contains-svgs
Steps to fix:
- Installed @svgr/webpack
- Added rule to use @svgr/webpack for .svg files. (used url-loader for .svg files from before).
- Added exclude rule for .svg files
Sample next.config.js:
const withTM = require('next-transpile-modules');
const withImages = require('next-images');
module.exports = withImages(
withTM({
transpileModules: [
'@dfo/components', // the package having this code: import { ReactComponent as SVG } from './logo-blue.svg
],
exclude: /\.svg$/,
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack", 'url-loader']
});
return config;
}
})
)
Has anyone solved this? I'm using create react app and typescript. Works fine in storybook but breaks when trying to use it in a component library build.
This fix only works in the Webpack-world
This seems to be caused by the fact that the Webpack plugin that adds the ReactComponent to each SVG that is imported somehow does not trigger on dynamic imports.
In the webpack config of CRA, you can see the configuration for this:
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
},
},
},
],
As you can see, the @svgr/webpack loader is used to generate the ReactComponent and this loader is prepended to the filepath you specified for the svg.
This means enforcing the same loader on your dynamic SVG import should fix your issues. The only difference is that the ReactComponent is now the default output.
import('!!@svgr/webpack?-svgo,+titleProp,+ref!./logo.svg').then((m) => {
console.log(m)
this.logoComponent = m.default
this.forceUpdate()
})
@thabemmz many thanks.
Any ideas how to make the import work in jest..? I'm using RTL..
test('render component', async () => {
const { getByTestId } = render(<App />);
await waitFor(() => {
const logo = getByTestId('dynamic-svg-logo');
expect(logo).toBeInTheDocument();
});
});

@thabemmz many thanks.
Any ideas how to make the import work in jest..? I'm using RTL.. ...
For now I've mocked SVGs..
// mock.js
import React from 'react';
const SvgrMock = React.forwardRef((props, ref) => <span ref={ref} {...props} />);
export const ReactComponent = SvgrMock;
export default SvgrMock;
And in package.json
"jest": {
"moduleNameMapper": {
"^.+\\.svg": "<rootDir>/path/to/mock.js"
}
}
@thabemmz ahh thank you for mentioning this default babel config! It was life-saver ;)
With the update to 5.0.0 to CRA,
!!@svgr/webpack?-svgo,+titleProp,+ref![svg_file_path] is not part of the config anymore, and isn't being resolved with the @svgr/webpack?-svgo,+titleProp,+ref!. I'm not seeing ReactComponent with default when importing dynamically like import(`${name}.svg). Are there any clues on how to go about doing this now?
I'm also facing an issue with the dynamic svg import now that the webpack config has changed
same here facing the same problem, any hero around there ?
@DaftPaul I ended up ejecting and editing the webpack config as suggested here https://github.com/webpack/webpack/discussions/15117#discussioncomment-1925385
can anyone explain to me why it works in codesandbox and not locally, with the EXACT same dependencies and versions?
https://codesandbox.io/s/blissful-cdn-9v8uk?file=/src/Icon.js
Came across this issue as well, and funnily enough I came across the same sandbox/dev.to post @kevinblilly and can't understand why that sandbox works. My only guess is that even though "react-scripts" are defined in package.json, the webpack.config used by it is being overwritten by a default one from codesandbox. Following the thread it seems it's not an easy issue to fix https://github.com/webpack/webpack/pull/10362
My solution to this was modifying the webpack.config with the package react-app-rewired. I don't like it, at all, but solved the problem.
Steps to use react-app-rewired:
- Install
npm install react-app-rewired --save-dev - Modify
package.jsonto contain :
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"}
- Create file
config-overrides.jswith content:
/* config-overrides.js */
module.exports = function override(config, env) {
config.module.rules = config.module.rules.map((rule) => {
if (rule.oneOf instanceof Array) {
return {
...rule,
oneOf: [
{
test: /\.svg$/,
use: [
{
loader: require.resolve("@svgr/webpack"),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve("file-loader"),
options: {
name: "static/media/[name].[hash].[ext]",
},
},
],
},
...rule.oneOf,
],
};
}
return rule;
});
return config;
};
This essentially maintains the current webpack.config but overwrites the section relating to @svgr/webpack to not have the issuer attribute which seems to break ContextModule behavior. Be mindful this change might have side effects, please check https://github.com/webpack/webpack/pull/10362
https://github.com/facebook/create-react-app/issues/5276#issuecomment-1042465657
Same problem here, is there a proper fix/issue already? The solution above works in my case.
I am struggling with dynamically importing icons as well :(, and I tried several approaches:
- In @LuisCor solution, that worked for @hansfeenstra1997 - which requires installing
@svgr/webpackand I get this error (I am not working in a new application but trying to refactor an existing one which hasfontawesome-freepackage):
node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg
TypeError: this.getOptions is not a function
By the way maybe this solution is with less configuration - https://stackoverflow.com/questions/55175445/cant-import-svg-into-next-js/70961634#70961634
- If I don't change the webpack configuration and I try to import the SVG icon with this line:
ImportedIconRef.current = (await import(/* webpackMode: "eager" */ `assets/iconsNew/${name}.svg`)).ReactComponent;
I am getting nothing. ImportedIconRef.current is undefined
- If I don't change the webpack configuration and I try to import the SVG icon with this line:
ImportedIconRef.current = (await import(/* webpackMode: "eager" */ `assets/iconsNew/${name}.svg`)).default;
I am getting this error:
Unhandled Rejection (InvalidCharacterError): Failed to execute 'createElement' on 'Document': The tag name provided ('/static/media/close.4ab55052.svg') is not a valid name.
I am trying to avoid ejecting like @mattpodolak
Same as @b-asaf with me as well.
Came across this issue as well, and funnily enough I came across the same sandbox/dev.to post @kevinblilly and can't understand why that sandbox works. My only guess is that even though "react-scripts" are defined in package.json, the webpack.config used by it is being overwritten by a default one from codesandbox. Following the thread it seems it's not an easy issue to fix webpack/webpack#10362
My solution to this was modifying the webpack.config with the package
react-app-rewired. I don't like it, at all, but solved the problem.Steps to use
react-app-rewired:
- Install
npm install react-app-rewired --save-dev- Modify
package.jsonto contain :"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test"}
- Create file
config-overrides.jswith content:/* config-overrides.js */ module.exports = function override(config, env) { config.module.rules = config.module.rules.map((rule) => { if (rule.oneOf instanceof Array) { return { ...rule, oneOf: [ { test: /\.svg$/, use: [ { loader: require.resolve("@svgr/webpack"), options: { prettier: false, svgo: false, svgoConfig: { plugins: [{ removeViewBox: false }], }, titleProp: true, ref: true, }, }, { loader: require.resolve("file-loader"), options: { name: "static/media/[name].[hash].[ext]", }, }, ], }, ...rule.oneOf, ], }; } return rule; }); return config; };This essentially maintains the current webpack.config but overwrites the section relating to
@svgr/webpackto not have theissuerattribute which seems to break ContextModule behavior. Be mindful this change might have side effects, please check webpack/webpack#10362
Expanding on this solution, which works.
If you are using aliases in your create-react-app use the following.
module.exports = function override (config, env) {
config.module.rules = config.module.rules.map(rule => {
if (rule.oneOf instanceof Array) {
return {
...rule,
oneOf: [
{
test: /\.svg$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{
removeViewBox: false,
}],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash].[ext]',
},
}
],
},
...rule.oneOf
],
}
}
return rule
})
return aliasWebpack(options)(config)
}
We're using react-app-rewired in combination with the customize-cra package.
LuisCor's solution worked without ejecting but with the customize-cra package:
const { addWebpackModuleRule, override } = require("customize-cra");
module.exports = override(
addWebpackModuleRule({
test: /\.svg$/,
use: [
{
loader: require.resolve("@svgr/webpack"),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve("file-loader"),
options: {
name: "static/media/[name].[hash].[ext]",
},
},
],
})
);
Running into the same issues when importing as https://github.com/facebook/create-react-app/issues/5276#issuecomment-1005415043.
Is there a solution to this without using react-app-rewired?
Having same issue here. As some time pasts, is there any solution to fix this without using external packages?
With the update to 5.0.0 to CRA,
!!@svgr/webpack?-svgo,+titleProp,+ref![svg_file_path]is not part of the config anymore, and isn't being resolved with the@svgr/webpack?-svgo,+titleProp,+ref!. I'm not seeing ReactComponent with default when importing dynamically likeimport(`${name}.svg). Are there any clues on how to go about doing this now?
If anyone still has this problem, this is my solution to fix it and there is no need for eject or third-party packages
- create file name .svgrrc in the root of the project
- add this content to it(which basically is the options you say here !!@svgr/webpack?-svgo,+titleProp,+ref)
{ "icon": true, "expandProps": false, "dimensions": false, "svgo": false, "titleProp": true } - and do this for the import
import(!!@svgr/webpack!${name}.svg)`
sources you can read for more:
- https://react-svgr.com/docs/configuration-files/
- https://react-svgr.com/docs/options/
- https://itecnote.com/tecnote/javascript-how-to-dynamically-import-svg-and-render-it-inline/ (I use this hook for dynamic import from a specific folder)