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.
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.
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.
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.
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 ) )
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 ) }) )
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.
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 🎊🎉
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With