Error passing required() Image as a prop - image

I have a file called data.js where I export an object, which one of its elements is an Image. This is how it looks:
var data = [
{
"id": 1,
"Category": "Category Title",
"Image": require("../images/comingsoon.png"),
"Summary": ""
},
And then in another component I import the data variable and I pass the data.Image to another component as a prop using FlatList.
<FlatList
style={{margin:5}}
numColumns={2}
data={this.state.data}
renderItem={({item}) => item}
/>
item looks like this:
<CategoryItem key={item.id} Image={item.Image} Category={item.Category}/>
And then I use the Image prop inside CategoryItem like so:
<Image source={this.props.item.Image} style={styles.CategoryImage}>
And this works perfectly!...
But I wanted to simply pass Item as a prop to CategoryItem like so:
<CategoryItem key={item.id} item={item}/>
And once inside CategoryItem, I would do:
render(){
const {Category, Image} = this.props.item;
And call the Image simply by doing
<Image source={Image} style={styles.CategoryImage}>
However, when I do that, the app crashes and it says that Image is a number.
After logging what item looks like, I found out that require()ing turns the image into a number, and it should work by simply passing it differently, it crashes. Any thoughts?

The issue is that the local variable Image is shadowing the component named Image.
Your code, simplified, might look something like this:
import { Image } from 'react-native';
class CategoryItem extends React.Component {
render() {
const { Category, Image } = this.props;
return <Image source={Image} />
}
}
Instead of the <Image /> declaration referring to the component as you'd expect, it refers to the Image prop.
This is easy to fix by renaming the variable:
class CategoryItem extends React.Component {
render() {
const { Category, Image: imageSource } = this.props;
return <Image source={imageSource} />
}
}
The reason the error tells you the component is a number is a implementation detail of how the React Native asset imports work. When you import an image with require('path.jpg'), React Native assigns that image a unique numeric ID and returns that instead of a image path descriptor. This is beneficial for performance reasons, so instead of passing a descriptor over the native-javascript bridge for each render, it can pass over a number, which are cheaper to serialize and to marshall.
As a side note, it's a common React practice to declare your component props in lowerCamelCase, so instead of <CategoryItem Image={} /> you would have <CategoryItem image={} />. This is just a convention, but in this case it would have avoided this error.

Related

Display image from a folder with React Native

I ran into a silly issue I don't know how to solve.
I have a screen with a camera that takes a photo and returns a path to the previous screen. In this screen, I want to display the photo that was taken. I have a path to the photo.
So the standard way to display an image is <Image style={styles.img} source={require('../path/img.png')} />
This is what I try to do:
</View>
{this.state.photoFile ? (
<Image style={styles.img} source={require(this.state.photoFile)} />
) : null}
</View>
I get an error Invalid call at line #
I tried to get around it with
this.state = { photoFile: '../../resources/logo.png' }
...
<Image style={styles.img} source={require(this.state.photoFile)} />
let img = require('../../resources/logo.png')
if (this.state.photoFile) {
img = require(this.state.photoFile)
}
and even
let path = '../../resources/logo.png'
if (this.state.photoFile) path = this.state.photoFile
let img = require(path)
But none worked.
How do I get around this? Thanks.
In React native ,all your images sources needs to be loaded before compiling your bundle.So you can not render Image from dynamic path in require.
you can use base64 to show image. Camera also provide base64 image.
if you want to create dynamic path for pre-builded Images that will be done by switch statement
for example
class App extends Component {
state = { avatar: "" }
get avatarImage() {
switch (this.state.avatar) {
case "car":
return require('./car.png');
case "bike":
return require('./bike.png');
case "bus":
return require('./bus.png');
default:
return require('./defualt.png');
}
}
render() {
return <Image source={this.avatarImage} />
}
}

Cannot capture events from custom component in NS6

I am using NS 6.0 Core. Testing on a physical Android device (have not tried this on iOS yet).
In a nutshell, I have nested components and in the inside component, I want to capture a custom event and pass it to the host component.
Inside component (called TopBar):
<StackLayout padding="10" orintation="horizontal" loaded="onLoaded">
<Label text="" class="wa" fontSize="24" vertcalAlignment="middle" tap="back" />
</StackLayout>
import { StackLayout } from 'tns-core-modules/ui/layouts/stack-layout';
import { EventData } from 'tns-core-modules/ui/core/view/view';
var stack: StackLayout;
let eventData: EventData = {
eventName: "onBackEvent",
object: stack
}
export function onLoaded(args) {
stack = <StackLayout>args.object;
}
export function back() {
stack.notify(eventData);
}
The host component
import { Page } from "tns-core-modules/ui/page/page";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout/stack-layout";
import { EventData, Observable } from "tns-core-modules/data/observable";
var model: Observable = new Observable();
var page: Page;
export function onLoaded(args: EventData) {
page = <Page>args.object;
var topBar: StackLayout = page.getViewById('topBar');
topBar.on('onBackEvent', () => {
console.log('go back');
});
page.bindingContext = model;
}
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:tb="components/shared/top-bar/top-bar" loaded="onLoaded" actionBarHidden="true">
<GridLayout rows="*, 75" columns="*">
<StackLayout class="page-content">
<tb:TopBar id="topBar" height="50"></tb:TopBar>
</StackLayout>
</GridLayout>
</Page>
Any ideas on what I might be missing?
Thanks
{N} automatically trims event names starting with on, hence onBackEvent will be recorded as BackEvent only. So notifying onBackEvent will not have any effect.
In my opinion it makes sense, also when I checked last time Angular didn't use to support event names prefixed with on with event binding. That could also be a reason they had to force this as a standard measure.
So, after hours of trial and error, I manage to make it work.
The issue is that when notifying the client, we have to declare the EventData object as such:
let eventData: EventData = {
eventName: "BackEvent",
object: stack
}
Note that we capture the event by subscribing to onBackEvent but setting up the event name as BackEvent. I am not sure why this works or where in the documentation it is written, but this change did the job.
If anyone has more information about this, please post it here so we can all learn.
Thanks.

How to make the images a button?

I am working on a react native app where I am trying to make some images act like a button so that when you press on them they print a statement in the console.
The images are displayed like this:
The code I have is:
class ImageList extends Component {
componentWillMount(){
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(this.props.images);
}
imageTouched(){
console.log('pressed');
}
renderRow(rowData){
const {uri} = rowData;
return(
<View style={{padding: 1, alignSelf: 'flex-start'}}>
<TouchableHighlight onPress={this.imageTouched}>
<Image style={styles.imageSize} source={{uri: uri}} />
</TouchableHighlight>
</View>
)
}
render() {
return (
<View>
<ListView
contentContainerStyle={styles.list}
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</View>
);
}
}
var styles = StyleSheet.create({
imageSize: {
//newWidth is the width of the device divided by 4.
//This is so that four images will display in each row.
width: newWidth,
height: newWidth,
padding: 2
},
list: {
flexDirection: 'row',
flexWrap: 'wrap'
}
});
When I run it there are no errors but when I touch the images nothing happens. I have checked the console but nothing is printed.
How do I get each image to act as a button?
Like others have mentioned, the problem is that this is not bound in the renderRow()-method. I think the easiest way to fix this is to change renderRow() to be an arrow-function:
renderRow = (rowData) => {
const {uri} = rowData;
return (
<View style={{padding: 1, alignSelf: 'flex-start'}}>
<TouchableHighlight onPress={this.imageTouched}>
<Image style={styles.imageSize} source={{uri: uri}} />
</TouchableHighlight>
</View>
);
}
Arrow function always have this set to their containing scope when invoked, so now this.imageTouched will resolve.
Notice that you don't have to do anything with your imageTouched()-function or invocation, since it's not referencing this.
PS. This syntax depends on Public Class Fields, which is a Stage 2 proposal of the language standard at the time of writing (likely to be included, already in use in internal React-code). This feature is possible to use with a babel-plugin that is enabled by default in React Native projects.
PS2. Note that declaring the method with an arrow function instead of using an arrow function in the invocation will create one instance of the method per component instance, instead of one instance per render. This should really be fine performance-wise.
React components defined as ES6 classes do not autobind methods' this context to the component instance. In this particular case, the renderRow callback is not bound and the context refers to the global application context instead, so the reference to this.imageTouched is undefined.
A common pattern you see a lot is to bind the callbacks in your render method:
<ListView
renderRow={this.renderRow.bind(this)}
/>
This, however, has the effect of creating a new instance of the function on every render, which causes unnecessary work for the garbage collector.
Another alternative is to use (lexically scoped) arrow functions and call the methods explicitly:
<ListView
renderRow={(data) => this.renderRow(data);
/>
But this has the same unwanted effect of unnecessarily creating functions on every render.
A slightly more verbose, but more "correct" way is to bind the callbacks in the class constructor:
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
}
Try this
<TouchableHighlight onPress={() => this.imageTouched()}>
Make sure to bind renderRow
renderRow={this.renderRow.bind(this)}

React Native: Render Image from props [duplicate]

I'm currently building a test app using React Native. The Image module thus far has been working fine.
For example, if I had an image named avatar, the below code snippet works fine.
<Image source={require('image!avatar')} />
But if I change it to a dynamic string, I get
<Image source={require('image!' + 'avatar')} />
I get the error:
Requiring unknown module "image!avatar". If you are sure the module is there, try restarting the packager.
Obviously, this is a contrived example, but dynamic image names are important. Does React Native not support dynamic image names?
This is covered in the documentation under the section "Static Resources":
The only allowed way to refer to an image in the bundle is to literally write require('image!name-of-the-asset') in the source.
// GOOD
<Image source={require('image!my-icon')} />
// BAD
var icon = this.props.active ? 'my-icon-active' : 'my-icon-inactive';
<Image source={require('image!' + icon)} />
// GOOD
var icon = this.props.active ? require('image!my-icon-active') : require('image!my-icon-inactive');
<Image source={icon} />
However you also need to remember to add your images to an xcassets bundle in your app in Xcode, though it seems from your comment you've done that already.
http://facebook.github.io/react-native/docs/image.html#adding-static-resources-to-your-app-using-images-xcassets
This worked for me :
I made a custom image component which takes in a boolean to check if the image is from web or is being passed from a local folder.
// In index.ios.js after importing the component
<CustomImage fromWeb={false} imageName={require('./images/logo.png')}/>
// In CustomImage.js which is my image component
<Image style={styles.image} source={this.props.imageName} />
If you see the code, instead of using one of these:
// NOTE: Neither of these will work
source={require('../images/'+imageName)}
var imageName = require('../images/'+imageName)
I'm just sending the entire require('./images/logo.png') as a prop. It works!
RELEVANT IF YOU HAVE KNOWN IMAGES (URLS):
The way I hacked my way through this problem:
I created a file with an object that stored the image and the name of the image:
export const ANIMAL_IMAGES = {
dog: {
imgName: 'Dog',
uri: require('path/to/local/image')
},
cat: {
imgName: 'Cat on a Boat',
uri: require('path/to/local/image')
}
}
Then I imported the object into the component where I want to use it and just do my conditional rendering like so:
import { ANIMAL_IMAGES } from 'path/to/images/object';
let imgSource = null;
if (condition === 'cat') {
imgSource = ANIMAL_IMAGES.cat.uri;
}
<Image source={imgSource} />
I know it is not the most efficient way but it is definitely a workaround.
Hope it helps!
If you're looking for a way to create a list by looping through a JSON array of your images and descriptions for example, this will work for you.
Create a file (to hold our JSON database) e.g ProfilesDB.js:
const Profiles = [
{
id: '1',
name: 'Peter Parker',
src: require('../images/user1.png'),
age: '70',
},
{
id: '2',
name: 'Barack Obama',
src: require('../images/user2.png'),
age: '19',
},
{
id: '3',
name: 'Hilary Clinton',
src: require('../images/user3.png'),
age: '50',
},
];
export default Profiles;
Then import the data in our component and loop through the list using a FlatList:
import Profiles from './ProfilesDB.js';
<FlatList
data={Profiles}
keyExtractor={(item, index) => item.id}
renderItem={({item}) => (
<View>
<Image source={item.src} />
<Text>{item.name}</Text>
</View>
)}
/>
Good luck!
As the React Native Documentation says, all your images sources needs to be loaded before compiling your bundle
So another way you can use dynamic images it's using a switch statement. Let's say you want to display a different avatar for a different character, you can do something like this:
class App extends Component {
state = { avatar: "" }
get avatarImage() {
switch (this.state.avatar) {
case "spiderman":
return require('./spiderman.png');
case "batman":
return require('./batman.png');
case "hulk":
return require('./hulk.png');
default:
return require('./no-image.png');
}
}
render() {
return <Image source={this.avatarImage} />
}
}
Check the snack: https://snack.expo.io/#abranhe/dynamic-images
Also, remember if your image it's online you don't have any problems, you can do:
let superhero = "spiderman";
<Image source={{ uri: `https://some-website.online/${superhero}.png` }} />
First, create a file with image required - React native images must be loaded this way.
assets/index.js
export const friendsandfoe = require('./friends-and-foe.png');
export const lifeanddeath = require('./life-and-death.png');
export const homeandgarden = require('./home-and-garden.png');
Now import all your assets
App.js
import * as All from '../../assets';
You can now use your image as an interpolated value where imageValue (coming in from backend) is the same as named local file ie: 'homeandgarden':
<Image style={styles.image} source={All[`${imageValue}`]}></Image>
Important Part here:
We cannot concat the image name inside the require like [require('item'+vairable+'.png')]
Step 1: We create a ImageCollection.js file with the following collection of image properties
ImageCollection.js
================================
export default images={
"1": require("./item1.png"),
"2": require("./item2.png"),
"3": require("./item3.png"),
"4": require("./item4.png"),
"5": require("./item5.png")
}
Step 2: Import image in your app and manipulate as necessary
class ListRepoApp extends Component {
renderItem = ({item }) => (
<View style={styles.item}>
<Text>Item number :{item}</Text>
<Image source={Images[item]}/>
</View>
);
render () {
const data = ["1","2","3","4","5"]
return (
<FlatList data={data} renderItem={this.renderItem}/>
)
}
}
export default ListRepoApp;
If you want a detailed explanation you could follow the link below
Visit https://www.thelearninguy.com/react-native-require-image-using-dynamic-names
Courtesy : https://www.thelearninguy.com
you can use
<Image source={{uri: 'imagename'}} style={{width: 40, height: 40}} />
to show image.
from:
https://facebook.github.io/react-native/docs/images.html#images-from-hybrid-app-s-resources
import React, { Component } from 'react';
import { Image } from 'react-native';
class Images extends Component {
constructor(props) {
super(props);
this.state = {
images: {
'./assets/RetailerLogo/1.jpg': require('../../../assets/RetailerLogo/1.jpg'),
'./assets/RetailerLogo/2.jpg': require('../../../assets/RetailerLogo/2.jpg'),
'./assets/RetailerLogo/3.jpg': require('../../../assets/RetailerLogo/3.jpg')
}
}
}
render() {
const { images } = this.state
return (
<View>
<Image
resizeMode="contain"
source={ images['assets/RetailerLogo/1.jpg'] }
style={styles.itemImg}
/>
</View>
)}
}
To dynamic image using require
this.state={
//defualt image
newimage: require('../../../src/assets/group/kids_room3.png'),
randomImages=[
{
image:require('../../../src/assets/group/kids_room1.png')
},
{
image:require('../../../src/assets/group/kids_room2.png')
}
,
{
image:require('../../../src/assets/group/kids_room3.png')
}
]
}
when press the button-(i select image random number betwenn 0-2))
let setImage=>(){
//set new dynamic image
this.setState({newimage:this.state.randomImages[Math.floor(Math.random() * 3)];
})
}
view
<Image
style={{ width: 30, height: 30 ,zIndex: 500 }}
source={this.state.newimage}
/>
I know this is old but I'm going to add this here as I've found this question, whilst searching for a solution. The docs allow for a uri: 'Network Image'
https://facebook.github.io/react-native/docs/images#network-images
For me I got images working dynamically with this
<Image source={{uri: image}} />
<StyledInput text="NAME" imgUri={require('../assets/userIcon.png')} ></StyledInput>
<Image
source={this.props.imgUri}
style={{
height: 30,
width: 30,
resizeMode: 'contain',
}}
/>
in my case i tried so much but finally it work StyledInput component name
image inside the StyledInput if you still not understand let me know
Say if you have an application that has similar functionality as that of mine. Where your app is mostly offline and you want to render the Images one after the other. Then below is the approach that worked for me in React Native version 0.60.
First create a folder named Resources/Images and place all your images there.
Now create a file named Index.js (at Resources/Images) which is responsible for Indexing all the images in the Resources/Images folder.
const Images = {
'image1': require('./1.png'),
'image2': require('./2.png'),
'image3': require('./3.png')
}
Now create a Component named ImageView in your choice of folder. One can create functional, class or constant component. I have used the Const component. This file is responsible for returning the Image depending on the Index.
import React from 'react';
import { Image, Dimensions } from 'react-native';
import Images from './Index';
const ImageView = ({ index }) => {
return (
<Image
source={Images['image' + index]}
/>
)
}
export default ImageView;
Now from the component wherever you want to render the Static Images dynamically, just use the ImageView component and pass the index.
< ImageView index={this.qno + 1} />
Create a constant where you save the image path including require, then in source put the name of that constant.
const image = condition ? require("../img/image1.png") : require('../img/image2.png');
<Image source={image} />
Here is a simple and truly dynamic solution to the problem if you have a bigger no of files.
[Won't work for Expo Managed]
Although the question is old I think this is the simpler solution and might be helpful. But I beg a pardon for any terminological mistakes, correct me please if I do any.
INSTEAD OF USING REQUIRE WE CAN USE THE URI WITH NATIVE APP ASSETS FOR ANDROID (AND/OR iOS). HERE WE WILL DISCUSS ABOUT ANDROID ONLY
URI can easily be manipulated as per the requirement but normally it's used for network/remote assets only but works for local and native assets too. Whereas require can not be used for dynamic file names and dirs
STEPS
Open android/app/src/main/assets folder from your App.js or index.js containing directory, if the assets folder doesn't exist create one.
Make a folder named images or any NAME of your choice inside assets, and paste all the images there.
Create a file named react-native.config.js in the main app folder containing App.js or index.js.
Add these lines to the new js file:
module.exports = {
project: {
ios: {},
android: {},
},
assets: ['./assets/YOUR_FOLDER_NAME/'],
};
at the place of YOUR_FOLDER_NAME use the newly created folder's name images or any given NAME
Now run npx react-native link in your terminal from main app folder, this will link/add the assets folder in the android bundle. Then rebuild the debug app.
From now on you can access all the files from inside android/app/src/main/assets in your react-native app.
For example:
<Image
style={styles.ImageStyle}
source={{ uri: 'asset:/YOUR_FOLDER_NAME/img' + Math.floor(Math.random() * 100) + '.png' }}
/>
You should use an object for that.
For example, let's say that I've made an AJAX request to an API and it returns an image link that I'll save to state as imageLink:
source={{uri: this.state.imageLink}}

Displaying Images in ListView for React Native

I have two presentational components. The first, called Category simply renders a React Native ListView component after doing a bit of set-up work.
The second component, Book, simply displays the data as text, with an image that should be fetched through the network.
Unfortunately, the Image doesn't seem to be displaying at all. Can someone help me get the image to display? Below, please find the Category and Book component definition, as well as a sample of the props being past to Book.
Category.js
import React from 'react';
import { ListView, Text } from 'react-native';
import _ from 'lodash';
import { camelizeKeys } from 'humps';
import Book from './Book';
const Category = ({ category }) => {
let element;
if (!_.isEmpty(category)) { // if data is available
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
const camelCasedBooks = camelizeKeys(category.results.books);
const data = ds.cloneWithRows(camelCasedBooks);
element = (
<ListView
dataSource={data}
enableEmptySections
renderRow={(book) => <Book {...book} />}
/>
);
} else { // if data is not available
element = (
<Text>Loading</Text>
);
}
return element;
};
Category.propTypes = {
category: React.PropTypes.object.isRequired,
};
export default Category;
Book.js
import React from 'react';
import { Image, Text, View } from 'react-native';
const Book = ({ author, bookImage, description, title }) =>
<View>
<Text>
{author}
</Text>
<Text>
{bookImage}
</Text>
<Text>
{description}
</Text>
<Text>
{title}
</Text>
<Image source={{ uri: bookImage }} />
</View>;
Book.propTypes = {
author: React.PropTypes.string.isRequired,
bookImage: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
};
export default Book;
Sample Book Props
{
author: "Vi Keeland",
bookImage: "https://s1.nyt.com/du/books/images/9781942215462.jpg",
description: "Reese dismisses a chance encounter with a smug stranger, until he turns out to be her new boss.",
title: "BOSSMAN"
}
When using images that come from a remote location (such as your bookImage you need to set an explicit width and height on the Image's style. This is due to the fact that there is not automatic way for react-native to know how big the images is going to be.
Check the Image documentation for strategies on calculating the Image's size before being rendered. In my experience however simply setting the width and height explicitely is enough most of the times.

Resources