react native use variable for image file - javascript

I know that to use a static image in react native you need to do a require to that image specifically, but I am trying to load a random image based on a number. For example I have 100 images called img1.png - img100.png in my directory. I am trying to figure out a way to do the following
<Image source={require(`./img${Math.floor(Math.random() * 100)}.png`)}/>
I know this intentionally does not work, but any workarounds would be greatly appreciated.

For anyone getting to know the react-native beast, this should help :)
I visited a couple of sites in the past too, but found it increasingly frustrating. Until I read this site here.
It's a different approach but it eventually does pay off in the end.
Basically, the best approach would be to load all your resources in one place.
Consider the following structure
app
|--img
|--image1.jpg
|--image2.jpg
|--profile
|--profile.png
|--comments.png
|--index.js
In index.js, you can do this:
const images = {
profile: {
profile: require('./profile/profile.png'),
comments: require('./profile/comments.png'),
},
image1: require('./image1.jpg'),
image2: require('./image2.jpg'),
};
export default images;
In your views, you have to import the images component like this:
import Images from './img/index';
render() {
<Image source={Images.profile.comments} />
}
Everybody has different means to an end, just pick the one that suits you best.
Da Man - Q: How is this answer using a variable?
Well, since require only accepts a literal string, you can't use variables, concatenated strings, etc. This is the next best thing. Yes, it still is a lot of work, but now you can do something resembling the OP's question:
render() {
var images = { test100: "image100" };
return (
<View>
<Text>
test {images["test" + "100"]}
</Text>
</View>
);
}

In JS require statements are resolved at bundle time (when the JS bundle is calculated). Therefore it's not supported to put variable expression as an argument for require.
In case of requiring resources it's even more trickier. When you have require('./someimage.png'), React Native packager will locale required image and it will be then bundled together with the app so that it can be used as a "static" resource when your app is running (in fact in dev mode it won't bundle the image with your app but instead the image will be served from the server, but this doesn't matter in your case).
If you want to use random image as a static resource you'd need to tell your app to bundle that image. You can do it in a few ways:
1) Add it as a static asset of your app, then reference to it with <Image src={{uri:'name_of_the_image_in_assets.png'}}/> (here is how you can add it to the native iOS app)
2) Require all the images upfront statically. Sth in a form of:
var randomImages = [
require('./image1.png'),
require('./image2.png'),
require('./image3.png'),
...
];
Then in your code you can do:
<Image src={randomImages[Math.floor(Math.random()*randomImages.length)]}/>
3) Use network image with <Image src={{uri:'http://i.imgur.com/random.jpg'}}/>

class ImageContainer extends Component {
this.state ={
image:require('default-img')
}
<View>
<Image source={this.state.image} />
</View>
}
In the context of this discussion,I had this case where wanted to dynamically assign images for a particular background. Here I change state like this
this.setState({
image:require('new-image')
})

I came to this thread looking for a way to add images in a dynamic way. I quickly found that passing in a variable to the Image -> require() was not working.
Thanks to DerpyNerd for getting me on the correct path.
After implementing the resources in one place I then found it easy to add the Images. But, I still needed a way to dynamically assign these images based on changing state in my application.
I created a function that would accept a string from a state value and would then return the Image that matched that string logically.
Setup
Image structure:
app
|--src
|--assets
|--images
|--logos
|--small_kl_logo.png
|--small_a1_logo.png
|--small_kc_logo.png
|--small_nv_logo.png
|--small_other_logo.png
|--index.js
|--SearchableList.js
In index.js, I have this:
const images = {
logos: {
kl: require('./logos/small_kl_logo.png'),
a1: require('./logos/small_a1_logo.png'),
kc: require('./logos/small_kc_logo.png'),
nv: require('./logos/small_nv_logo.png'),
other: require('./logos/small_other_logo.png'),
}
};
export default images;
In my SearchableList.js component, I then imported the Images component like this:
import Images from './assets/images';
I then created a new function imageSelect in my component:
imageSelect = network => {
if (network === null) {
return Images.logos.other;
}
const networkArray = {
'KL': Images.logos.kl,
'A1': Images.logos.a1,
'KC': Images.logos.kc,
'NV': Images.logos.nv,
'Other': Images.logos.other,
};
return networkArray[network];
};
Then in my components render function I call this new imageSelect function to dynamically assign the desired Image based on the value in the this.state.network:
render() {
<Image source={this.imageSelect(this.state.network)} />
}
Once again, thanks to DerpyNerd for getting me on the correct path. I hope this answer helps others. :)

Here is a simple and truly dynamic solution(no renaming or import required) 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' }}
/>

Related

How to export static images on Nextjs?

when I want to export my nextjs app, it says that I cannot export my images on static websites.
Error: Image Optimization using Next.js' default loader is not compatible with next export.
Possible solutions:
- Use next start to run a server, which includes the Image Optimization API.
- Use any provider which supports Image Optimization (like Vercel).
- Configure a third-party loader in next.config.js.
- Use the loader prop for next/image.
How can I make it so that it does ?
Is there a way for me to simply tell it to render images statically ? I dont want to go throught other onlines images loaders..
I created a npm module so that we can use the Next.js <Image/> component with optimized images while using the static export functionality.
https://www.npmjs.com/package/next-image-export-optimizer
The library wraps the <Image /> component of Next.js and automatically creates optimized images using sharp.js at export time.
It uses a custom loader to create a srcset for the <img /> that the <Image /> component of Next.js creates. Then at build/export time, the images inside the public/images folder (as an example) are optimized with sharp.js and copied into the build folder.
You need to set up a custom image loader in Next.js
In your next.config.js file, add this property to the export:
images: {
loader: "custom"
}
And make a script called loader.js that exports this:
function imageLoader({ src }) {
return `/images/${src}`; // REPLACE WITH YOUR IMAGE DIRECTORY
}
module.exports = imageLoader;
For each Image component, set the loader prop manually:
const imageLoader = require("PATH TO loader.js");
<Image loader={imageLoader} />
I'm going to add onto skara9's answer because it didn't quite work for me. I found a thread on github discussing it and the answer there worked for me. It just wraps around the NextJS image component and works pretty flawlessly for me.
// components/Image.js
import NextImage from "next/image";
// opt-out of image optimization, no-op
const customLoader = ({ src }) => {
return src
}
export default function Image(props) {
return (
<NextImage
{...props}
loader={customLoader}
/>
);
}
Make sure you change your imports and update your next.config.js
import Image from '../components/Image.js'

Conditionally import assets in create-react-app

Is it possible to conditionally import assets when creating a React app using create-react-app? I'm aware of the require syntax - example:
import React from "react";
const path = process.env.REACT_APP_TYPE === "app_1" ? "app_1" : "app_2";
const imagePath = require(`./assets/${path}/main.png`);
export default function Test() {
return (
<img src={imagePath} alt="" />
);
}
This however bundles all my assets no matter what.
It will load the proper image, but it will still bundle all the files together in the final build.
When I look in the dev tools for the final build, I can see all the assets there even though I only wanted to load the assets for app_1.
Am I forced to touch the webpack config, if so, what should I change? or is there another way?
In the days when React didn't exist we didn't put assets into our JS files. We let the CSS to decide, what assets to load for what selectors. Then you could simply switch a corresponding class on or off for a corresponding element (or even the whole page) and viola it changes color, background, or even a form. Pure magic!
Ah. What times these were!
All above is true and I do not understand why would anyone do or recommend doing it differently. However if you still want to do it (for any reason) - you can! Latest create-react-app comes with out-of-the-box support for lazy loading of arbitrary components via dynamic importing and code splitting. All you need to do is use parenthesized version of the import() statement instead of the regular one. import() takes in a request string as usual and returns a Promise. That's it. Source code of the dynamicaly requested component won't be bundled in, but instead stored in separate chunks to be loaded on demand.
Before:
import OtherComponent from './OtherComponent';
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
After:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
Notice how function MyComponent part is identical.
For those wondering if it is tied to CRA or React, it's not. It's a generic concept that can be used in vanilla JavaScript.
You will need to use webpack (or other bundler.) The code is not being run when it's bundled, so the compiler has no way of knowing which branch of logic to follow (app_1 or app_2). Therefore you have to get into the bundler's logic in order to achieve your goal.
However, this isn't as scary as it seems since webpack has built in capability to do this (no 3rd parties required...)
I would look into using webpack.providePlugin
(https://webpack.js.org/plugins/provide-plugin)
or its sibling DefinePlugin
(https://webpack.js.org/plugins/define-plugin)
(I'm afraid these examples are off the top of my head, so it's very unlikely they'll work on first pass.)
Examples:
Both will require a provider module...
// in path/provider.js
module.exports = {
live: '/path/to/live/image',
dev: '/path/to/dev/image'
}
Provide Plugin Example
// in webpack
new webpack.ProvidePlugin({
imagePath: [
'path/provider', // the file defined above
process.env.ENVIRONMENT // either 'dev' or 'live'
]
}),
// in code
export default function Test() {
return (
<img src={imagePath} alt="" />
);
}
Define Plugin example:
// in webpack
new webpack.DefinePlugin({
'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT)
});
// in code
var providers = require('path/provider'); // same path provider as above
export default function Test() {
return (
<img src={providers[process.env.ENVIRONMENT]} alt="" />
);
}
In both cases the bundler is forced to collapse your variable to an actual literal value at compile time - before bundling has taken place. Since you have now collapsed the logical path down to a single option, it is now free to only bundle the relevant assets.
You can't do this with default CRA settings.
Because if your dynamic require or dynamic import path is not static, webpack won't be able to determine which assets to include in the final build folder, therefore, it will grab everything from your ./src folder, and put them all to your build folder.
There is a way to do it with default CRA settings
You can add to .env something like
REACT_APP_SKIN=1
Put your skin assets in public/css1, public/css2 etc. And include them in public/index.html using code like
<link href="/css%REACT_APP_SKIN%/theme.css" rel="stylesheet">

Get asset path in Laravel from within React component?

I'm wondering what the best way is to get the asset path for things like images from inside a React component. I'm using Laravel, and the built-in asset() function that you can use in Blade files is exactly what I need, but I need it on the front-end in JS. Seems like Webpack territory, but I'm not sure what setting I need to modify.
The problem arises when my path is more than just the hostname, for example "localhost:3000/myproject/public" instead of just "localhost:3000".
Doing something like....
import myImage from '../../images/my-image.png'
...
<img src={myImage} />
results in a src path of "/images/my-image.png", which is not found, because the image actually resides at "/myproject/public/images/my-image.png"
In the past I've used the not-so-elegant solution of doing:
// react.blade.php
<body data-root-url={{ asset('') }}>
...
</body>
// helpers.js
export const rootUrl = document.querySelector('body').getAttribute('data-root-url')
While this hasn't gotten me into any trouble yet, I'm trying to find a better way, and I also started using Typescript recently and it (understandably) gets cranky about this because it technically could be null.
I've also tried the following in webpack.mix.js:
mix.webpackConfig({
output: {
publicPath: 'http://localhost:3000/myproject/public/',
},
})
but that doesn't seem to have any effect on image paths. Oddly enough though, this did work when running into a similar issue with code-splitting and looking for the JS chunks.
Any thoughts? Thanks!
I fix it by adding a new controller in the routes/web.php file.
Route::get('assets/{path}', function ($path) {
return response()->file(public_path("assets/$path"));
});
Then I added the images inside the folder public/assets/img/test.jpg and called it in my React component normally.
<Card sectioned>
<EmptyState image="/assets/img/test.jpg">
</EmptyState>
</Card>

React Native: How to make modular components using local image and data assets?

create-react-native-app v1.0.0
react-native-cli 2.0.1
react-native 0.52.0
Android API 25
I have several JSON files in ./src/data (aliased as data) and images in ./src/assets/images (aliased as images).
I am making a list of images that are filterable and sortable based on the corresponding data. Additionally, I have different types of images the user can toggle between for compact and expanded view.
From my searching it looks like my plan to reference the image path dynamically from a data object property is not possible and I need to do something like:
import image1 from 'images/'image1.png';
import image2 from 'images/'image2.png';
<Image source={some_expression ? image1 : image2} />
or
<Image source={some_expression ?
require('images/image1.png') : require('images/image2.png')} />
That works for the toggle, but I still have the original problem where I can't actually assign image1 and image2 from my data objects such as:
var image1 = "images/" + data.image1.img
where data.image1.img is the filename.
Is there a way around this where I can dynamically reference the local images for the corresponding data in a reusable component?
Short of hardcoding individual components with the images for each data object, I'm at a loss.
I'm new to React and mobile in general so I may have overlooked something in the documentation or online searching.
You can import your local images in parent of your component and use it as a prop.
<Image source={this.props.localImage} />
Where 'localImage' is imported in parent, like in code below and passed as a prop to child (your component).
import localImage from 'images/some-image.png';
This will maintain reusability and works perfectly.
For anyone with the same issue, here is what I did in order to use a string from my JSON data to reference the corresponding image to change what is shown dynamically.
In Images.js import all static images:
const images = {
img1: require('images/img1.png'),
imgNth: require('images/imgNth.png')
}
export default images;
or alternatively
import _img1 from 'images/img1.png';
import _imgNth from 'images/imgNth.png';
const images = {
img1: _img1,
imgNth: _imgNth
}
export default images;
Then where you want to use them i.e. App.js:
import images from 'images/Images';
import data from 'data/Data';
<Image source = {images[data[index].imgName]} />
Where imgName is a property that contains the exact same string value as one of the properties in the imported images i.e. img1 or imgNth in this example.
Regardless of how many images you have, I would suggest writing a simple script to run through your image folder and auto-generate an Images.js type file. It isn't hard to do and will save you a lot of work, especially if you add or remove images regularly during development so you don't have to manually update it each time.

Dynamically requiring images for React/Webpack OR what is cost of many require(img)

I am creating a React component that randomly cycles through a large number of background images. From my understanding, I could ignore Webpack here and just dynamically generate the require/import statements and pass them to the component.
However, if I want Webpack to use a loader and generate hashes, etc - I haven't found a solution.
This question is similar, but the responses don't address the problem:
Dynamically Add Images React Webpack
My current solution involves just requiring all of the images in the component - however, I'm wondering what the 'costs' of this are - just an increased bundle size? Attached is sample code to demonstrate what I mean:
import React from 'react';
import styles from './Background.css';
const pic0 = require('./imgs/0.jpg');
const pic1 = require('./imgs/1.jpg');
const pic2 = require('./imgs/2.jpg');
const pic3 = require('./imgs/3.jpg');
// etc etc etc
class Background extends React.Component {
constructor(props) {
super(props);
this.state = {
pic: {},
};
this.changeBackground = this.changeBackground.bind(this);
}
componentWillMount() {
this.setState({ pic: pic0 });
}
componentDidMount() {
// set timeout here
}
changeBackground() {
// change background via component state here
}
render() {
return (
<img className={styles.background} src={this.state.pic} />
);
}
}
export default Background;
Thoughts? Any help would be greatly appreciated.
It depends on which loader you configure webpack to use to load these images.
url-loader
When you use url loader, the contents of the image will be base64-encoded and embedded in your JavaScript bundle.
Thus the more images you require, the larger the bundle that the browser must download initially. The advantage is that all of the images are loaded in the browser and can display immediately.
file-loader
When you use file-loader, the image file is copied to your distribution folder (and usually renamed with a content hash) and the URL of the image is stored in your JavaScript bundle.
Requiring a lot of images has very little impact on the size of your JavaScript bundle. The app starts faster. The downside is that the browser will have to fetch the images from the server on demand as you display them.
Summary
url-loader: larger JS bundle, all images display instantly, harder to leverage image cache
file-loader: smaller JS bundle, images individually retrieved when displayed, makes better use of image cache.
Based on your problem statement (randomly cycle through large number of images), I'd start with file-loader.
The good news is your JS code is unchanged either way: you just treat the imported images as URLs either way. Its just a webpack reconfiguration if you change your mind.
Since you have a whole folder of images, I'd also recommend making use of webpack context to essentially require the whole folder in one line:
const imageReq = require.context("./imgs", false, /\.jpg$/);
setUrl(i) {
// "require" a specific image e.g. "./4.jpg"
const url = imageReq(`./${i}.jpg`);
this.setState({pic: url});
}
// ...

Categories

Resources