Img rendering issue in React

Hey there,

I’ve run into a real finicky issue with images not rendering unless they have the require() method AND the relative path is explicitly hardcoded into the src=().

I need to be able to swap out the hardcoded src for an object property because this is a component that is mapped over to generate tiles with unique image thumbnails.

Here’s the only combination I’ve gotten to render the image on my local dev host:

import React from 'react';
import { useSelector } from 'react-redux';
import { selectContentTiles } from '../contentTilesSlice';

export default function ContentTile({ id }) {
    const tiles = useSelector(selectContentTiles);
    const tile = tiles[id];
    const image = require('../../../images/content-test-thumbnail-1.png');

    return (
        <div key={tile.id} className="content-tile">
            <img src={image} alt="Thumbnail" className="content-thumbnail" />
            <h3>{tile.name}</h3>
            <p>{tile.description}</p>
            <div className="tile-tags">
                {Object.keys(tile.tags).map(tagId => createTag(tagId))}
            </div>
        </div>
    );
    
    function createTag(tagId) {
        const tag = tile.tags[tagId];

        return (
            <a key={tag.tagId} className="content-tag">{tag.tagName}</a>
        );
    }
}

Here’s what I want, but won’t render (where tile.image references the absolute path from the initialState object, but I can’t get an absolute path even hardcoded to render):

import React from 'react';
import { useSelector } from 'react-redux';
import { selectContentTiles } from '../contentTilesSlice';

export default function ContentTile({ id }) {
    const tiles = useSelector(selectContentTiles);
    const tile = tiles[id];
    const image = require(tile.image);

    return (
        <div key={tile.id} className="content-tile">
            <img src={image} alt="Thumbnail" className="content-thumbnail" />
            <h3>{tile.name}</h3>
            <p>{tile.description}</p>
            <div className="tile-tags">
                {Object.keys(tile.tags).map(tagId => createTag(tagId))}
            </div>
        </div>
    );

    function createTag(tagId) {
        const tag = tile.tags[tagId];

        return (
            <a key={tag.tagId} className="content-tag">{tag.tagName}</a>
        );
    }
}

Imports should not be inside the function definition.
What does your state look like (selectContentTiles)? The argument passed in ‘require’ would be a file path. Is that really what you get back from ‘tiles[id]’?

What import are you seeing inside the function definition?

tiles[id] is just selecting the specific tile within the tiles selector, then (in the second example posted) image should be assigned to the image key selected from that tile (require(tile.image)) which should be returning the specific file path from the state.

Here’s what the contentTilesSlice looks like with the initialState:

import { createSlice } from '@reduxjs/toolkit';
import allContentTilesData from '../../data';

export const contentTilesSlice = createSlice({
    name: 'contentTiles',
    initialState: {
        contentTiles: {
            '0': {
                id: 0, 
                name: 'The Bee Movie', 
                description: 'According to all known laws of aviation, there is no way a bee should be able to fly.', 
                image: 'src/images/contest-test-thumbnail-1.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'},
                    {tagId: 3, tagName: 'Tag 3'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            },
            '1': {
                id: 1, 
                name: 'Shrek 1', 
                description: 'Think it\'s in there?', 
                image: 'src/images/contest-test-thumbnail-2.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            },
            '2': {
                id: 2, 
                name: 'Shrek 2', 
                description: 'Once upon a time in a kingdom far, far away, the king and queen were blessed with a beautiful baby girl.', 
                image: 'src/images/contest-test-thumbnail-3.png', 
                tags: [
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'}
                ]
            },
            '3': {
                id: 3, 
                name: 'Test Content 3', 
                description: 'Example text for content description 3.', 
                image: 'src/images/contest-test-thumbnail-1.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'},
                    {tagId: 3, tagName: 'Tag 3'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            },
            '4': {
                id: 4, 
                name: 'Test Content 4', 
                description: 'Example text for content description 4.', 
                image: 'src/images/contest-test-thumbnail-2.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'},
                    {tagId: 3, tagName: 'Tag 3'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            },
            '5': {
                id: 5, 
                name: 'Test Content 5', 
                description: 'Example text for content description 5.', 
                image: 'src/images/contest-test-thumbnail-3.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'},
                    {tagId: 3, tagName: 'Tag 3'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            },
            '6': {
                id: 6, 
                name: 'Test Content 6', 
                description: 'Example text for content description 6.', 
                image: 'src/images/contest-test-thumbnail-1.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'},
                    {tagId: 3, tagName: 'Tag 3'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            },
            '7': {
                id: 7, 
                name: 'Test Content 7', 
                description: 'Example text for content description 7.', 
                image: 'src/images/contest-test-thumbnail-2.png', 
                tags: [
                    {tagId: 0, tagName: 'Tag 0'}, 
                    {tagId: 1, tagName: 'Tag 1'},
                    {tagId: 2, tagName: 'Tag 2'},
                    {tagId: 3, tagName: 'Tag 3'},
                    {tagId: 4, tagName: 'Tag 4'}
                ]
            }
        }
    },
    reducers: {
        addTile: (state, action) => {
            const { id } = action.payload;
            state.contentTiles[id] = action.payload;
        }
    }
});

export const { addTile } = contentTilesSlice.actions;
export const selectContentTiles = (state) => state.contentTiles.contentTiles;
export default contentTilesSlice.reducer;

From what I understand, you also tried the import syntax here:

require() and import are Node.js functions, so they would run in your node environment, not the browser, so I’d put them out of the function scope. And why do you need require() in the first place as the file path is a property of tile?

I’m also not sure about this being an absolurte path. Still looks like a relative path to me. Once your react app is compiled, this path does not exist anymore as your compiled app is in the build folder. So I think you either need to store a truely absolute path in your Redux state or import/require a file path at the top of your js file that is relative to that js file (which I guess is inside the ‘src’ folder.

1 Like

Thanks for the advice, I’ll try to rework these to fix those issues. I’m brand new to using React/Redux/Redux Toolkit and been struggling to find examples for guidance because they all seem to use different syntax/toolkits/versions.

I started using require() because without it the images won’t render and just default to showing the alt="" for the

I found that suggestion here: https://stackoverflow.com/questions/34582405/react-wont-load-local-images

Ok, looks like your idea is right to use require() inside the function. So one thing I assume is that you plan your relative file path from the slice file, not from your ContentTile file?
So if you have an images folder with all your images you store in the redux state, you could have a relative file path in your ContentTile and store only the image name in your redux store. So if you have this folder structure:

src
|__components
    |__ContentTile.js
|__assets
    |__images
        |__image.jpg

You could try this in ContentTile:

const tiles = useSelector(selectContentTiles);
    const tile = tiles[id];
    const image = require(`../assets/images/${tile.image}`);

With this being the file path in your slice:

id: 0, 
  name: 'The Bee Movie', 
  description: 'According to all known laws of aviation, there is no way a bee should be able to fly.', 
  image: 'image.jpg',
...

Would that work?

1 Like

This worked! Thanks for your diligent help.

Will this relative path assigning to a const still work in a compiled build of the app? I have a feeling this isn’t the best practice way of managing images and paths that a React developer would do in a production app… So if you have any tips on best practice way of doing this I’d love to learn them!

1 Like

You’re welcome, glad it worked out!
I haven’t worked with require() inside the function scope this way yet but always imported images at the top of the file. But if it works in the preview, it should work in the compiled app as webpack should take care of that then.
I also store images images in an assets/images folder in the src folder, which is a common practice according to this article:

I’m just not sure about your use of the Redux store as your data seems to be rather static? In that case a simple data object might be a more fitting option as it is needs less resources.