Import image dynamically in React component
Asked Answered
B

6

29

I have an Articles component that shows a blog page with listed articles.

render() {
    const articles = {
        ...this.state.articles
    }

    const article = Object.keys(articles).map(cur => {
        return <Article
            key={this.state.articles[cur].id}
            imgName={this.state.articles[cur].thumb}
            title={this.state.articles[cur].title}
            meta={this.state.articles[cur].meta}
            clicked={() => this.detailedHandler(this.state.articles[cur].id)}
            detailed={this.state.articles[cur].detailed} />
    });

As you can see I pass image name with props to Article component. I want then to display the appropriate image for each article.

How do I import an image in Article component based on the props I receive (props.imgName) from Articles component?

Babirusa answered 14/12, 2018 at 8:28 Comment(2)
The image name would need to then be added to the end of a url string where you have stored the image on a server. Then pllaced as the src to an html <img src=https://myWebsite.com/images/${this.prop.imgName}.jpg />Systaltic
I have my images in src/assets folder. And if I understand correctly after webpack builds the project these images name and path are changed. So I cant use relative url like thatBabirusa
B
23

I used context.

const images = require.context('../../../assets/img', true);
loadImage = imageName => (assets(`./${imageName}`).default);
<img src={loadImage("someimage.png")} alt="" />

I don't know if this is an optimal solution, but it works.

Babirusa answered 14/12, 2018 at 12:38 Comment(4)
Inline image is injected in this case that cause performance issues.Danielldaniella
This code does not make sense. Where did you get assets function? What's require.context? require doesn't have any context in itRiyadh
@JerryGreen It is a webpack function https://mcmap.net/q/88362/-what-is-require-contextHuelva
Instead of using loadImage = imageName => (assets(`./${imageName}`).default); you can use this const loadImage = imageName => (images(`./${imageName}`).default);Histone
C
21

For anyone looking for a modern approach using async-await and custom react hooks, I found a pretty slick solution. Create a file called useImage.js and paste the following code:

import { useEffect, useState } from 'react'

const useImage = (fileName) => {
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState(null)
    const [image, setImage] = useState(null)

    useEffect(() => {
        const fetchImage = async () => {
            try {
                const response = await import(`../assets/img/${fileName}`) // change relative path to suit your needs
                setImage(response.default)
            } catch (err) {
                setError(err)
            } finally {
                setLoading(false)
            }
        }

        fetchImage()
    }, [fileName])

    return {
        loading,
        error,
        image,
    }
}

export default useImage

Then just import the custom hook into your image component, mine looks something like this:

import useImage from '../../hooks/useImage'

import Typography from './Typography' // simple plain-text react component

const Image = ({ fileName, alt, className, ...rest }) => {
    const { loading, error, image } = useImage(fileName)

    if (error) return <Typography>{alt}</Typography>

    return (
        <>
            {loading ? (
                <Typography>loading</Typography>
            ) : (
                <img
                    className={`Image${
                        className
                            ? className.padStart(className.length + 1)
                            : ''
                    }`}
                    src={image}
                    alt={alt}
                    {...rest}
                />
            )}
        </>
    )
}

export default Image

The nice thing about this solution is that no matter where your component is in relation to your assets folder, the react hook is always in the same place, so the relative path stays the same.

Commodus answered 18/11, 2021 at 17:20 Comment(2)
This is working very well. I'm not using the Typography class, so I just have if (error) return {alt}; However, when the file name is wrong, I get error "Error: Objects are not valid as a React child (found: object with keys {alt}). If you meant to render a collection of children, use an array instead."Doura
I just got the file error case to work by adding useEffect to error case like so: useEffect(() => {if (error != null) {return {alt};}}, [alt, error]);Doura
S
19

You can load images dynamically from the API response with dynamic imports that is Stage 3 proposal as of now.

The resulting code should look something like:

loadImage = imageName => {
  import(`./assets/${imageName}.jpg`).then(image => {
    this.setState({
      image
    });
  });
};
render() {
  const { image } = this.state;
  return (
    <Fragment>
      {image && <img src={image} alt="" />}
    </Fragment>
  );
}

View Codesandbox Demo

This feature is supported out of the box in create-react-app, If using other systems, you can use the Babel plugin

Sporting answered 14/12, 2018 at 10:2 Comment(6)
React.lazy is used for importing components, so you would have to import the image into a component firstSporting
Now this would be the most correct answer I think. This could be used to include assets like images, or services or modules in our final build depending on an environment variable, which is nice and just what I need. Do you know if it's possible to import all images of given a path, I mean, load all images in first component? Thank you.Skyjack
@Skyjack I would normally use an import-all.macro in that scenarioSporting
you probably missed image.defaultBluefarb
Oops... The Codesandbox Demo does not exist anymore!Alcmene
This is the best aproach. Just to share my experience, you need to write the import with the text of the image like in the answer. It doesn't work with a constant like this: const name = `./assets/${imageName}.jpg` import(name). Still trying to figure out why this does not work.Tautomerism
B
4

there are my way, works nicely:)

import React, {useState} from "react"

const getFavorites = (props) => {

  const {item, favouritesNote} = props;
  const [image, setImage] = useState("");

  (function (imageName) {
    import(
      `../../../../assets/images/chart/favorite${imageName ? "_active" : ""}.png`
    ).then((image) => setImage(image.default));
  })(favouritesNote[item.id]);

  return (
    <div>{image && <img alt="" className="img-responsive" src={image} />}</div
  )
}
Bluefarb answered 29/6, 2021 at 12:35 Comment(1)
Great answer! I think if you will change the variable name from favouritesNote to imageSrc it will be more clear.Bunko
H
2

Note: All these comments work complementary with the @jadenRose hook solution which is a great abstraction.

You can import the images dynamically with the native js dynamic import('') , there's no need for React Lazy.

However, you have to be aware of the path/content you pass to the import(path) because depending on how you are planning to include the images to import in the final bundle there could be restrictions. There are two main ways:

Note: This is for Rollup, in Webpack i didn't try it but should be similar

a)- Making rollup automatically include the possibles files to import while creating the bundle using the official plugin https://www.npmjs.com/package/@rollup/plugin-dynamic-import-vars please read the doc and notice how there are important restrictions in the 'path' string, essentially you should just set the file name in the variable, the rest of the path have to be fixed in the import('') in order to provide rollup a restricted scope to import. eg:

OK

import(../assets/img/${fileName}.svg)

Wrong

import(filePath)

b)- Include in the bundle programmatically the files you can dynamically import example

//rollup.config.js 
import copy from 'rollup-plugin-copy';

plugins: [
    copy({
        targets: [ { src: 'src/assets/icons/*', dest: 'lib/assets/icons' },],
    }),
           …        
],

With this option b) you have no restrictions on the variable content but have to be careful with what you included in the bundle.

Conclusion: You can use dynamic import(...) but if you not properly handle the files inclusion on the bundle, it can be possible they are excluded and then the dynamic import will fail in the consumer.

Huelva answered 19/8, 2022 at 16:59 Comment(0)
N
0

I found this worked best for me: I created an index file inside the images folder. there I imported all the images I have and created a class component with the variables assigned to each image import. this is because when we import an image in react using the import statement, that is, import someImage from './somewhere' react assigns the 'someImage' variable to a module with a 'static media' address, my terminology there might be wrong. Here is the example:

import image13_1 from './image13_1.png';
import image13_2 from './image13_2.png';
import image13_3 from './image13_3.png';
import image13_4 from './image13_4.png';
import image13_5 from './image13_5.png';
import image13_6 from './image13_6.png';
import image13_7 from './image13_7.png';

export class IMG{

    1= image13_1

    2 = image13_2

    3 = image13_3

    4 = image13_4

    5 = image13_5

    6 = image13_6

    7 = image13_7

  }
  export default IMG;

from here I just import the IMG class and create an instance of it and call the image number a property:

var img = new IMG()
console.log('img',img[1]
Nonrigid answered 15/4, 2022 at 9:34 Comment(2)
This won't work if you have no access to the images folder. You might be accessing the images from an API, for example.Cosmopolitan
User asked for dynamic importing. This images are stablished beforehand. No dynamic here.Tautomerism

© 2022 - 2024 — McMap. All rights reserved.