Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 517
WiseOlMan Avatar asked Aug 18 '17 10:08

WiseOlMan


People also ask

How do I add an SVG image to react?

There are a few ways to use an SVG in a React app: Use it as a regular image. Import it as a component via bundler magic (SVGR) Include it directly as JSX.

How do I use SVG in Create react app?

Use SVG as an ImageUsing the img tag is how Create React App embeds the logo SVG, which is defined in a separate file, src/logo. svg . Then it can be invoked as an image in JSX: In line 2, the import statement tells webpack to use this image.


Video Answer


2 Answers

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.

like image 141
skylize Avatar answered Sep 20 '22 03:09

skylize


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 🎊🎉

like image 25
Christian Avatar answered Sep 21 '22 03:09

Christian