Best way to pass Icon / Img URLs in React as Props?

I would like to create a “card style” list of workouts. The mode is one of a handful of types: run, bike, swim, strength, rest, stretch for example. There is an icon with each exercise mode.

I can insert the proper icon like I have show below (see “this works” section).

However when I try to abstract the data into a JSON file and import it into the App.js file, I am stumped on the best way to get the img URLs for the icons. Passing them as props seems to be a mystery, and I’m getting confused about relative vs absolute URIs for the icons, which are located in the img folder. App.js is in the root, and the components are in /src/components/

Let me know if this is enough info to understand my question.

Folder Structure
root
App.js
-src
–components
—DailyWorkouts.js
—WorkoutList.js
-img
–runImg.png

This works:

App.js

import React from 'react'
import WorkoutList from './components/WorkoutList'
import Header from './components/Header'
// import DailyWorkout from './components/DailyWorkout'

import runImg from './img/run.png'

class App extends React.Component {
  render() {
    return (
      <div>
        <WorkoutList
          dailyData={[
            {
              mode: 'run',
              size: '24px',
              distance: 3,
              units: 'miles',
              icon: runImg
            }
          ]}

        />
      </div>
    )
  }
}



export default App

WorkoutList.js

class WorkoutList extends React.Component {
  render() {
    return (
      <div>{
        this.props.dailyData.map((dailyData,i) => {
          return (
            <DailyWorkout
              key={i}
              mode={dailyData.mode}
              icon={dailyData.icon}
            />
          )
        })
      }
      </div>
    )
  }
}

DailyWorkout.js

class DailyWorkout extends React.Component {

  render() {
    return (
      <div >
        <img
        alt={this.props.mode}
        src={this.props.icon}
        />
      <span>
            {this.props.mode}
      </span>
      </div>
    )
  }
}

However, this does not

App.js

import WorkoutList from './components/WorkoutList'

import runImg from './img/run.png'
import bikeImg from './img/bike.png'
import swimImg from './img/swim.png'

import dailyWorkouts from './dailyWorkouts'

class App extends React.Component {
  render() {
    return (
      <div>
        <WorkoutList
          dailyData={dailyWorkouts}
        />
      </div>
    )
  }
}

dailyWorkouts.json

[
   {
     "mode": "run",
    "size": "24px",
    "distance": 3,
    "units": "miles",
    "icon": "runImg"
  }, {
    "mode": "bike",
    "size": "24px",
    "distance": 24,
    "units": "miles",
    "icon": "bikeImg"
  },
  {
    "mode": "swim",
    "size": "24px",
    "distance": 3000,
    "units": "meters",
    "icon": "swimImg"
  }
  ]

I have tried passing in the same URL that worked in the example above within the JSON file, instead of the name assigned in the import section of App.js

This seems like a simple thing but I’m stumped. I feel like I shouldn’t have to pass the URL in, because there are a limited number of exercise modes, and the icon always remains the same for each mode (running figure for a run workout, swimming figure for swim workout, etc)

In your .json file, the property icon of each object literal in the array is a string. So, you’re passing an icon string to DailyWorkout instead of the icon ‘itself’ (imported from png files).

Instead of importing the info from a .json file you can import from a .js file that exports the array.
From this .js you can import the images.

For example

dailyWorkouts.js

import runImg from './img/run.png';
import bikeImg from './img/bike.png';


export default [
 { 
  /* other properties */,
  icon: runImg
 },
 { 
  /* other properties */,
  icon: bikeImg
 },
 ...
]

From your App.js you can do:
import dailyWorkouts from './path/to/dailyWorkouts'

in this case, dailyWorkouts[0].icon is in fact the icon object, not a string

1 Like

You must create state before tray export json date in case with class constructor and super i think its better way use function instead classes in that case use hooks useState

This looks great, thanks, i’ll let you know how it works.

Does it matter if these are in public or not? (using create-react-app)

I’m not following your answer, but it intrigues me. Do you have an example?

While using create-react-app, you can’t import from outside the src folder, so importing from public will cause errors. They ensure this because the app is not configured to transpile files outside of src, which may cause other errors, and because importing would cause Webpack to bundle the imports and include them into a file in public anyway (so the build size would be increased by having duplicates of the images).

This answer from SO is more complete, take a look

But there’s an error in my previous answer. The src attribute from the img element is indeed just a string*

<img src="/path.png"/>

This element makes a GET request to the path /path.png. You’d have to setup the server to respond to this request and give the corresponding image.
I think create-react-app automatically serves files from the public folder, but in that case you wouldn’t be importing, they’d be required in the client side.

Sorry if I may have confused you

*Edit: well, the src may be a string, but not necessarily. You can also do it the way I stated (and you too). Lucky me.

In this case you could put the img files inside src and import them into your components.

1 Like
import runImg from '../img/run.png'
import bikeImg from '../img/bike.png'
import swimImg from '../img/swim.png'

class DailyWorkout extends React.Component {

  render() {
    let iconObj =
      {"run": runImg, "bike": bikeImg, "swim": swimImg}
    return (
        <img
        alt={this.props.mode}
        src={iconObj[this.props.mode]}
    
      </div>
    )
  }
}

export default DailyWorkout

This worked! Thanks for your help. As logn as my iconObj contains keys that match the exercise mode (run/bike/swim/etc) I can use the key to display the icon. Yay!

2 days to solve that. It’s been over a year since I coded in React or Javascript so I’m pretty happy.