How to import an entire folder of SVG images (or how to load them dynamically) into a React Web App?
Asked Answered
C

6

28

I have a component that takes in an :itemName and spits out an html bundle containing an image. The image is different for each bundle.

Here's what I have:

import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';

import SVGInline from "react-svg-inline";

export default (props) => (
  <NavLink className="hex" activeClassName="active" to={'/hex/' + props.itemName}> {React.createElement(SVGInline, {svg: props.itemName})} </NavLink>
)

How could I make this component work?

I know that if I just imported all my images explicitly, I could just call my images like so...

import SVGInline from "react-svg-inline";
import SASSSVG from "./images/sass.svg";

<NavLink className="hex" activeClassName="active" to="/hex/sass"><SVGInline svg={ SASSSVG } /></NavLink>

This would work, but since I need to include ~60 svgs, it adds A LOT of excess code.

Also, I found in this question this code...

import * as IconID from './icons';

But that doesn't seem to work (it was part of the question, not the answer), and the answer was a bit too nonspecific to answer the question I'm asking.

I also found this question but again there's an answer (although unapproved) that possess more questions than it answers. So, after installing react-svg, I set up a test to see if the answer works like so...

import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { NavLink } from 'react-router-dom';
import ReactSVG from 'react-svg'

export default (props) => (
  <NavLink className="hex" activeClassName="active" to={'/hex/' + props.itemName}>
    <ReactSVG
      path={"images/" + props.itemName + ".svg"}
      callback={svg => console.log(svg)}
      className="example"
    />
  </NavLink>
)

But, just as the OP of that question was asking, the page can't find my svg even after copying my entire image folder into my build folder. I've also tried "./images/"

I feel like I'm just missing one last key piece of information and after searching for the past day, I was hoping someone could identify the piece I'm missing.

Cannice answered 18/8, 2017 at 10:42 Comment(0)
F
29

If using React, I strongly suspect you are also using Webpack. You can use require.context instead of es6 import and Webpack will resolve it for you when building.

require.context ( folder, recurse, pattern )
  • folder - String - Path to folder to begin scanning for files.
  • recurse - Boolean - Whether to recursively scan the folder.
  • pattern - RegExp - Matching pattern describing which files to include.

The first line of each example ...

const reqSvgs = require.context ( './images', true, /\.svg$/ )

... creates a Require Context mapping all the *.svg file paths in the images folder to an import. This gives us a specialized Require Function named reqSvgs with some attached properties.

One of the properties of reqSvgs is a keys method, which returns a list of all the valid filepaths.

const allSvgFilepaths = reqSvgs.keys ()

We can pass one of those filepaths into reqSvgs to get an imported image.

const imagePath = allSvgFilePaths[0]
const image = reqSvgs ( imagePath )

This api is constraining and unintuitive for this use case, so I suggest converting the collection to a more common JavaScript data structure to make it easier to work with.

Every image will be imported during the conversion. Take care, as this could be a foot-gun. But it provides a reasonably simple mechanism for copying multiple files to the build folder which might never be explicitly referenced by the rest of your source code.

Here are 3 example conversions that might be useful.


Array

Create an array of the imported files.

const reqSvgs = require.context ( './images', true, /\.svg$/ )
const paths = reqSvgs.keys ()

const svgs = paths.map( path => reqSvgs ( path ) )

Array of Objects

Create an array of objects, with each object being { path, file } for one image.

const reqSvgs = require.context ( './images', true, /\.svg$/ )

const svgs = reqSvgs
  .keys ()
  .map ( path => ({ path, file: reqSvgs ( path ) }) )

Plain Object

Create an object where each path is a key to its matching file.

const reqSvgs = require.context ('./images', true, /\.svg$/ )

const svgs = reqSvgs
  .keys ()
  .reduce ( ( images, path ) => {
    images[path] = reqSvgs ( path )
    return images
  }, {} )

SurviveJS gives a more generalized example of require.context here SurviveJS Webpack Dynamic Loading.

Formless answered 14/9, 2017 at 4:7 Comment(6)
I made a small edit to actually assign the imported svgs to a variable so you can use them. :)Formless
Hey skylize, looks good but I'm curious, how would you target a specific image within the variable svgs?Cannice
@Cannice Great question, and surprisingly complex to say. I've updated my answer to give proper treatment to "How do I use these once I've got them?"Formless
Thanks for the info, that's just what I was looking for.Cannice
My usecase was to show background images, used it like that: const icons = require.context('./assets', true, /\.svg$/); const paths = icons.keys(); const iconList = paths.map( path => icons(path).default); that returns the static path for each SVG iconElectoral
require is not defined errorStethoscope
A
13

Stumbled onto this issue - I initially had the "Accepted answer", but i caused http request for each and every svg, which triggered a rate limit. So I ended up with a combination the accepted answer and what @karthik proposed - using a loader in the request.context

As of CRA 2.0 @svgr is included to import svg's as react components.

const reqSvgs = require.context('!@svgr/webpack!flag-icon-css/flags/4x3', true, /\.svg$/)

So here we combine an svg loader and require.context

const flagMap = reqSvgs.keys().reduce((images, path) => {
  const key = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
  images[key] = reqSvgs(path).default
  return images
}, {})

Then we map all these into a json object so we can use key lookup

To render svg's in jsx:

const Flag = flagMap['dk']
return (
  <Flag />
)

And happy days, svgs included in bundle and no individual http requests 🎊🎉

Acoustician answered 13/9, 2019 at 8:46 Comment(1)
I tried this but it doesn't work. flagMap contains static path to svg, but it can not be rendered like that. Here is the error: [mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[observer]' DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('/static/media/wine.61384ebe.svg') is not a valid name.Bethannbethanne
C
5

Instead of multiple SVG files you can use the single SVG sprite.

SVG sprite can be generated from a directory of SVG files using svg-sprite-generator:

svg-sprite-generate -d images -o images/sprite.svg

Then use it like this:

import React from 'react';
import { NavLink } from 'react-router-dom';
import sprite from './images/sprite.svg';

export default (props) => (
  <NavLink className="hex" activeClassName="active" to={'/hex/' + props.itemName}>
    <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
      <use xlinkHref={`${sprite}#${props.itemName}`} />
    </svg>
  </NavLink>
)
Confess answered 11/9, 2017 at 14:50 Comment(1)
This is also a great solution though it requires a bit more set up than skylize's answer below.Cannice
F
1

Best way is to use a node module like [SVG to React Loader] (https://github.com/jhamlet/svg-react-loader)

Feeding answered 17/9, 2017 at 6:57 Comment(0)
S
0

For Vite (Vue but should work for other libraries) just use

const iconsModule = import.meta.glob('@/path/*.svg');

then you can iterate through paths and get the file name with or without format name

for (const path in iconsModule) {
            const icon = path.split('/').slice(-1)[0];
            this.icons.push(icon);
        }
Supermarket answered 17/12, 2023 at 11:25 Comment(0)
E
-1

You can simply make a function which takes a parameter "name" (your svg icon name) and return your svg code.

import React from 'react' export function getIcon(name){ switch(name) { case 'back': return ( // your svg code here <svg></svg> ) } }

And then you can import it anywhere and simply call it with icon name you want.

import { getIcon } from './utils'
render() {
  return (
    <div>
     <span>{ getIcon('back') }</span>
    </div>
  )
}
Egghead answered 18/8, 2017 at 10:55 Comment(7)
This still means that in "./utils" I'll have to have ~60 svg imports before the function and then the function seems pointless because once I have all those imports, it's rather trivial to write the code as it's simply <InlineSVG path={importedSVG}/> but I want to get rid of the import aSVG from "./path/to/image/a"; import bSVG from "./path/to/image/b"; import cSVG from "./path/to/image/c";Cannice
Oh, you don't want to write any code for svg import. You have one folder containing all your svg icons and you want to use those somehow, is it?Egghead
Yes, exactly. Any suggestions?Cannice
Thinking. But still man you have to somewhere write your SVG icon name, I mean you have to call your particular icon.svg, right? (assuming you're not importing)Egghead
It's not like that you can iterate over the icons and automate the process, assuming they all have different names. (not icon-1, icon-2, ...etc)Egghead
Right, but essentially, if I do it the normal way I have 60 imports + 60 uses. If I take the uses and put it into a component that loads the ones I need I have still 60 imports and since they're display by sections I can add some logic to my names so that would be 60 imports + 5 uses. Now, if I could just find a way to reduce those 60 imports, my code could look IMMENSELY cleaner. Because maybe later down the line, I have 120 imports...Cannice
I was suggesting to directly return your SVG code instead of importing. You copy your SVG code of icon and put it inside the switch case and return it. That way you are isolating all your SVGs in separate file and then just importing the single function ( you are not importing SVG's). I guess this way your code will look cleaner, if you care about it and not about automating things (You may endup writing more code if you automate). Other way you might have to make sprite of SVGs and import that.Egghead

© 2022 - 2024 — McMap. All rights reserved.