Dito
Dito

Reputation: 995

Import image dynamically in React component

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?

Upvotes: 30

Views: 57311

Answers (6)

Juanma Menendez
Juanma Menendez

Reputation: 20229

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.

Upvotes: 2

Muna Lombe
Muna Lombe

Reputation: 11

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]

Upvotes: 0

Jaden Rose
Jaden Rose

Reputation: 271

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.

Upvotes: 27

Abbos Tajimov
Abbos Tajimov

Reputation: 1169

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

Upvotes: 4

Dito
Dito

Reputation: 995

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.

Upvotes: 23

Agney
Agney

Reputation: 19224

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

Upvotes: 19

Related Questions