Create-react-app: Dynamic import doesn't work with SVG imported as ReactComponents

Created on 3 Oct 2018  路  10Comments  路  Source: facebook/create-react-app

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

  1. 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

  1. Create file with following content
import { ReactComponent as Logo } from './logo.svg'
export default Logo
  1. Use dynamic import as usual for this file
bug needs investigation

Most helpful comment

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()
    })

All 10 comments

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:

  1. Installed @svgr/webpack
  2. Added rule to use @svgr/webpack for .svg files. (used url-loader for .svg files from before).
  3. 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()
    })
Was this page helpful?
0 / 5 - 0 ratings

Related issues

AlexeyRyashencev picture AlexeyRyashencev  路  3Comments

alleroux picture alleroux  路  3Comments

stopachka picture stopachka  路  3Comments

DaveLindberg picture DaveLindberg  路  3Comments

oltsa picture oltsa  路  3Comments