React-native: Setting <Image /> source with string concatenation fails ('Requiring unknown module...')

Created on 28 Aug 2015  ยท  31Comments  ยท  Source: facebook/react-native

I've seen this mentioned before: https://github.com/facebook/react-native/issues/282#issuecomment-96908438

I'm trying to include an image from within the project, the name of which is dependant on variables.

var imageName = `image!${var1}_of_${var2}`
contents = (
  <Image source={require(imageName)} style={styles.card} onPress={this._onPress} />
)

The above fails with the usual:

Requiring unknown module "image!somwething_for_somethingelse". If you are sure the module is there, try restarting the packager.

These also fail in the same manner:

<Image source={require(image!${var1}_of_${var2})}>
<Image source={require('image!' + {var1} + '_of_' + {var2})} >

This works, no problem:

<Image source={require('image!somwething_for_somethingelse')}>

I've also tried

  • Stop and re-build Xcode project
  • Restarting the packager
Locked

Most helpful comment

this 'feature' is retarded and is annoying. Would very much be interested how i can bypass this stupid check

WHILE i agree that it is a good idea to stop developers from including images which consist of concatenated strings, it is the developer's responsibility to code in such way that won't cause him trouble in the future. A much better solution for react-native would be to print a notice to the console alerting them of what they just done.

All 31 comments

Right, there's an easy solution here, I don't think this is actually a bug.

source={{ uri: imageName }}

We intentionally don't support dynamically generated image names because it makes it very hard to manage assets over time, just like how you wouldn't want to do

var MyComponent = require('./' + libName + typeOfComponent);

or something like that. Dynamic image name generation like this also won't work with the new dynamic asset managing system we're rolling out which let's you add and update assets on the fly with just cmd+R (no more need to add to Xcode and recompile).

Question regarding the suggestion of @gavinsmith:

I was not aware you can do this for static image resources. What are the disadvantages of using { uri: "imageName" } instead of require("image!imageName")?

this 'feature' is retarded and is annoying. Would very much be interested how i can bypass this stupid check

WHILE i agree that it is a good idea to stop developers from including images which consist of concatenated strings, it is the developer's responsibility to code in such way that won't cause him trouble in the future. A much better solution for react-native would be to print a notice to the console alerting them of what they just done.

@matejkramny

The images need to be able to statically analyzed, so that the packager could resolve them and package in the app automatically. Hence dynamic strings with require syntax is not possible.

It's the same as bundling JavaScript with browserify or webpack. Your JavaScript filenames need to be statically analyzed and bundled.

If you want to use dynamic strings, you sure can. But you've to manually package up the assets. You can use the following syntax.

<Image source={{ uri: "image" + "name" }} />

@satya164 I am not talking about images here.

React-native is using node.js, so i'm organising my code a bit like i do on server apps.

directory tree:

.
โ”œโ”€โ”€ commonStyles.js
โ”œโ”€โ”€ img
โ”‚ย ย  โ””โ”€โ”€ logo.png
โ”œโ”€โ”€ network.js
โ”œโ”€โ”€ storage.js
โ””โ”€โ”€ views
    โ”œโ”€โ”€ index.js
    โ”œโ”€โ”€ loading.js
    โ”œโ”€โ”€ login.js
    โ”œโ”€โ”€ profile.js

views/index.js

[
  'loading',
  'login',
  'profile',
].forEach((file) => {
  exports[file] = require('./' + file);
});

but have instead got to do:

exports.loading = require('./loading');
exports.login = require('./login');
exports.profile = require('./profile');

I am doing this so that whenever I require('./views') i can reference any component without extra requires.

@matejkramny No, React Native doesn't use NodeJS.

If you've worked with client side apps and bundling JavaScript with webpack or browserify, that's how it works. The JavaScript files need to be statically analyzed in order to be bundled. You cannot use dynamic strings.

I see, got a bit confused as its got node_modules, require and all that.

Thanks for explaining

Hey guys, I'm trying to use the manual versions of this using source={{uri: "imagename"}} but it's not finding my files that in the same directory as the component rendering them.. How should my files be organized/ where should they be in order for this to work?

@naji247 You've to put the files under the res/drawable like you'd add images in Android.

@satya164 but how to statically analyzed? There's no res/drawable directort

@nollydeng The folder drawable must be created under the already existing android/app/src/main/res. Add a file such as

android/app/src/main/res/drawable/myImage.png

and reference it in code as

<Image source={{uri: "myImage"}} />

@aicioara But what about iOS?

@dasmikko For iOS you need to include them as an asset, eg by placing them in your project much the same as you would a custom font or any other file. From that point on you can refer to the image by its exact filename (for iOS, including the extension, for Android, without).

Usage

So your image tag could be generically used as follows:

import { Platform, Image } from 'react-native

// ...

// render fn returns:
<Image source={{ uri: `filename${Platform.OS === 'ios' ? '.jpg' : ''}` }} />

For your convenience it might be worth making a quick wrapper around Image, call it something like ManuallyBundledImage (bad name, I know), and use that instead for the images you include manually.

Pixel density variants

iOS

iOS will automatically use @2x and @3x variants if you append that to their filenames. You do not need to change the uri in the image tag -- it should automatically grab the proper pixel density based on device:

// images to add to Xcode:
filename.jpg
[email protected]
[email protected]

Android

For android, you can use variants for pixel densities by placing ldpi, mdpi, hdpi, xhdpi, etc variants in the proper folders as follows:

android/app/src/main/res/drawable/filename.jpg // default fallback image
android/app/src/main/res/drawable-ldpi/filename.jpg // different resolution
... // other dpi variants
android/app/src/main/res/drawable-xxxhdpi/filename.jpg // different resolution

Thats not really multiplatform solution. Is there any possible library or plugin which solves this for me?

same problem. how could I concat my variable to the image path... Is there any other solution?

Same issue as above. If React Native is designed this way, then how do you handle a situation where you do an API call and render a local image based on what you get back?

You must enumerate the local images, like

const IMAGES = {
  image1: require('../image1'), // statically analyzed
  image2: require('../image2'), // statically analyzed
}

getImage(num: number) { // dynamically invoked
  return IMAGES['image' + num];
}

@sahrens, thanks! This worked perfectly for me.

In my component, I imported my equivalent of your IMAGES and used it in my Image element:

<Image source={IMAGES[image.key]} />

@sahrens what if we have unknown number of images,, it can be 100 or 1000, we cant known the # of images at build time...

@vvavepacket Not sure what you use case would look like, but you might find the require-all module useful

If you don't know how many there are, then they can't be local static assets installed with the APK. At the very least, you can codegen a file with all the requires. If you are loading images off the network, then just set the source uri property like normal.

For now I'm defining a thumbnails object in a constants file:

export const thumbnails = {
  'orange'  : require('../assets/thumb-orange.png'),
  'blue'    : require('../assets/thumb-blue.png'),
  'teal'    : require('../assets/thumb-teal.png')
}

Then I import that object into my component. During the render() call, I'm doing:

const thumbnail = thumbnails[this.props.color]
<Image source={thumbnail} />

The error message "Unknown named module" is totally misleading!!! For me this error message is a bug.

Please add this to docs at https://facebook.github.io/react-native/docs/image.html

An Explanation with example like the following one on stack overflow and @satya164 's good explanation about the static analysis for packaging requirement + best practise
https://stackoverflow.com/questions/38776713/react-native-image-component-requiring-unknown-module

It's not something you would expect to happen (I haven't seen 32 and 56 thumbs down very often). But you have a perfect reason for doing it like this. So please don't let the people in the dark - this issue has been opened mid 2015 and is still visited ;)

@RobIsHere Have you considered tackling this documentation issue yourself by submitting a PR? :-)

No. For me the best solution would be fixing the error message. "this error message is a bug". Probably someone who knows this code of Image source could do it in minutes. Docs is only plan B ;)

@sahrens I just wanted to give you a shout out and tell you that you are a gentleman and a scholar. Those who came here later in 2017 with this issue, that solution worked immediately for me.

Got a working solution, though not recommended for large images, works perfectly for (a lot of)small images.

Steps:

  1. Convert the icon(s) to base64 string(s).
  2. Create a JSON file with filename as the keys and the base64 strings as values.
    (You can also store them to a local database)

e.g.
ImageData.json

{

"icon1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQ.......==",
"icon2": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQ.......=="

}

3.Import the json file to the place where you require the images dynamically.

e.g.

const imageData = require("./images/ImageData.json")

4: Get/generate the key/filename at runtime. and get the image source.

e.g.

const imageSrc = imageData[keyname]

5: Generate a image dynamically at runtime.

e.g.

<Image style={{ width: 70, height: 70, resizeMode: Image.resizeMode.contain }} source={ uri: imageSrc } />

Done..

Extra..
Written a helper python script to automate the json file creation.

import base64
import os

directory = os.fsencode('.')

with open('ImagesData.json', 'wb') as jsonFile:
    jsonFile.write(bytes('{', 'utf-8'))

    written = False

    for file in os.listdir(directory):
        filename = os.fsdecode(file)

        if filename.endswith('.png'):
            with open(filename, "rb") as image_file:
                if written:
                    jsonFile.write(bytes(',\n','utf-8'))
                encoded_string = base64.b64encode(image_file.read())
                jsonFile.write(bytes(('"' +filename+ '":'), 'utf-8'))
                jsonFile.write(bytes('"data:image/png;base64,', 'utf-8') + encoded_string + bytes('"', 'utf-8'))
                written = True

    jsonFile.write(bytes('}', 'utf-8'))
  1. Copy the script to the image folder and run the script (requires python 3.6).
  2. A json file will the created with image name as key and base64 string as values.
  3. Copy the file to project and use (You can delete the images after that).
  4. Use the json file as mentioned above.

I got working solution for this it may be helpfull
Just put the images URI or file location in array like this

  Nav_items:[{sourcess:require('../icons/s.png'),names:'Home'},
            {sourcess:require('../icons/s.png'),names:'Deals'},{sourcess:require('../icons/s.png'),names:'fashion'},
            {**sourcess:require('../icons/s.png')**,names:'mobile & accessories'},
            {sourcess:require('../icons/s.png'),names:'kitchen and dining'},
            {sourcess:require('../icons/s.png'),names:'Home,tools & decor'},
            {sourcess:require('../icons/s.png'),names:'sports & fitness'},
            {sourcess:require('../icons/s.png'),names:'computer & software'}
            ,{sourcess:require('../icons/s.png'),names:'Baby & toys'},
            {sourcess:require('../icons/s.png'),names:'Perfumes & Beauty'},
            {sourcess:require('../icons/s.png'),names:'CAMERAS'},
            {sourcess:require('../icons/s.png'),names:'Track order'}
            ,{sourcess:require('../icons/s.png'),names:'my account'}
            ,{sourcess:'../icons/s.png',names:'new feed'}
        ,{sourcess:require('../icons/s.png'),names:'wishlist'}],

Then refer the image as like

<List dataArray={this.state.Nav_itemss}
            renderRow={(item) =>
              <ListItem>
                  <View style={{flexDirection:'row'}}>
                  **<Image style={{width:35,height:25}} 
                  resizeMode="center"
                  source={item.sourcess}/>**
                <Text>{item.names}</Text>                
                    </View>
              </ListItem>
            }>
          </List>

I am using a list to feed my navigation drawer item its working fine for me

@sahrens Good idea! Thank you!

I attempted to use require statements inside a JSON file but this fails. Converting the JSON to a .js file and formatting it as above is the workaround I've now used.

https://stackoverflow.com/questions/51370371/dynamic-local-images-in-expo

Was this page helpful?
0 / 5 - 0 ratings