Reputation: 10673
Totally new to React Native (and React).
I have:
export default class MyApp extends Component {
// Initialize the hardcoded data
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([
'property1', 'property2', 'property3', 'property4'
])
};
}
render() {
return (
<View style={{flex: 1, paddingTop: 50}}>
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Image style={styles.image} source={require('./images/' + {rowData} + '.jpg')} />}
/>
</View>
);
}
}
I am trying to show four images (stored in an images folder within the same directory as index). I've hardcoded the image names (property1, property2 etc). This is giving me an error Unknown named module: './images/[object Object].jpg'
Looking at the image page in the official docs(https://facebook.github.io/react-native/docs/images.html) I would like to create a variable to store the full path first and then use something like: <Image source={path} />
as per the docs.
Also, would it be advisable to create a separate function for renderRow?
Upvotes: 0
Views: 618
Reputation: 9701
Let's review the different aspects of your question:
Unknown named module: './images/[object Object].jpg'
You are passing an array of string
to your dataSource
, so it would make sense for rowData
in renderRow
to have the string value, that you could concatenate directly to './images'
, however, you are mixing JavaScript and JSX 'contexts' (for lack of a better term). When you do:
(rowData) => <Image style={styles.image} source={require('./images/' + {rowData} + '.jpg')}
Whatever you pass inside the {}
as the value of a prop is evaluated as JavaScript, so source={require('./images/' + {rowData} + '.jpg')}
will be pure JavaScript. Thus, when you wrap rowData
in curly braces inside this 'context', you are converting it to an object, and since you are using the new ES6 'shortcuts' for object literals, what you are basically doing is instantiated an object with a key rowData
that has the value of rowData
. In essence, your transpiler is doing something like this:
(rowData) => <Image style={styles.image} source={require('./images/' + {rowData: rowData} + '.jpg')}
JavaScript attempts to convert the object to string
, being the default representation of course [object Object]
. I would replace it by the following (note that it is still wrong in terms of image handling in React Native. I'll provide a solution for that later):
(rowData) => <Image style={styles.image} source={require('./images/' + rowData + '.jpg')}
Or even better:
(rowData) => <Image style={styles.image} source={require(
./images/${rowData}.jpg)}
You can read more about template strings on MDN.
Now, to the problem with images, as stated in the docs, the name of static assets have to be known statically (that is, on 'transpilation' time), so a dynamic name generated by string concatenation is not valid. I think that the reasoning for this is to make possible to identify all resources in the App so that they can be packaged in the bundle.
Nonetheless, there are simple workarounds for this. In your case, you can do something like this:
const property1 = require('./images/property1.jpg');
const property2 = require('./images/property2.jpg');
const property3 = require('./images/property3.jpg');
const property4 = require('./images/property4.jpg');
const images = {property1, property2, property3, property4}; // ES6 literal objects. This basically means {property1: property1, property2: property2, ...
// Now images are known statically
export default class MyApp extends Component {
// Initialize the hardcoded data
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([
'property1', 'property2', 'property3', 'property4'
])
};
}
render() {
return (
<View style={{flex: 1, paddingTop: 50}}>
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Image style={styles.image} source={images[rowData]} />}
/>
</View>
);
}
}
As for your last question: I'd say that creating a separate function only depends on the 'size' of the function: If you are just doing a simple mapping, as you do in the piece of code that you posted, a separate function would not be advisable, as the code is more readable this way. But if you are going to do 2-3 lines of computation, it might be advisable to wrap your functionality in an external function.
Upvotes: 1
Reputation: 8936
basically you can't do this with React Native. You would have to probably require them at the top of your file and work with them as variables instead of dynamic paths. Heres a quote from their dev team:
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).
Upvotes: 0
Reputation: 3866
This example was taken from my own project and it is working properly.
<Image style={styles.facebookImage} source={require('../statics/facebook-logo-white.png')}></Image>
What you can do to set the source dynamicalli is to use the state, like this:
<Image style={styles.facebookImage} source={require(this.state.imageRoute)}></Image>
Of course you have to set up properly the state component like this:
this.setState({imageRoute: 'your rute here'});
If I were you I would use a separate renderRow function in order to get the code more readeable, try something like this:
_renderRow(rowData) {
return (
<Image style={styles.facebookImage} source={require(this.state.imageRoute)}></Image>
);
}
And dont forget to include Image from react-native like this:
import {
...
Image
} from 'react-native';
Hope it helps you.
Upvotes: 0