Not allow to require by using variable? [duplicate] - reactjs

I have read several posts about issues that people are having with React Native and the require() function when trying to require a dynamic resource such as:
Dynamic (fails):
urlName = "sampleData.json";
data = require('../' + urlName);
vs. Static (succeeds):
data = require('../sampleData.json');
I have read on some threads that this is a bug in React Native and in others that this is a feature.
Is there a new way to require a dynamic resource within a function?
Related Posts (all fairly old in React time):
Importing Text from local json file in React native
React Native - Dynamically List/Require Files In Directory
React Native - Image Require Module using Dynamic Names
React Native: how to use require(path) with dynamic urls?

As i have heard of, react's require() only uses static url not variables, that means that you have to do require('/path/file'), take a look at this issue on github and this one for more alternative solutions, there are a couple of other ways to do it!
for e.g
const images = {
profile: {
profile: require('./profile/profile.png'),
comments: require('./profile/comments.png'),
},
image1: require('./image1.jpg'),
image2: require('./image2.jpg'),
};
export default images;
then
import Images from './img/index';
render() {
<Image source={Images.profile.comments} />
}
from this answer

Here is my solution.
Setup
File 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)} />
}
The value passed into the imageSelect function could be any dynamic string. I just chose to have it set in the state first and then passed in.
I hope this answer helps. :)

For anyone reading this that cannot work with the existing answers, I have an alternative.
First I'll explain my scenario. We have a mono repo with a number of packages (large react-native app). I want to dynamically import a bunch of locale files for i18n without having to keep a central registry in some magic file. There could be a number of teams working in the same monorepo and the DX we want is for package developers to be able to just add their local files in a known folder {{packageName}}/locales/en.json and have our core i18n functionality pick up their strings.
After several less than ideal solutions, I finally landed on https://github.com/kentcdodds/babel-plugin-preval as an ideal solution for us. This is how I did it:
const packageEnFiles = preval`
const fs = require('fs');
const path = require('path');
const paths = [];
const pathToPackages = path.join(__dirname, '../../../../packages/');
fs.readdirSync(pathToPackages)
.filter(name => fs.lstatSync(path.join(pathToPackages, name)).isDirectory())
.forEach(dir => {
if (fs.readdirSync(path.join(pathToPackages, dir)).find(name => name === 'locales')) {
const rawContents = fs.readFileSync(path.join(pathToPackages, dir, 'locales/en.json'), 'utf8');
paths.push({
name: dir,
contents: JSON.parse(rawContents),
});
}
});
module.exports = paths;
`;
Then I can just iterate over this list and add the local files to i18next:
packageEnFiles.forEach(file => {
i18n.addResourceBundle('en', file.name, file.contents);
});

If you need to switch between multiple locally stored images, you can also use this way:
var titleImg;
var textColor;
switch (this.props.data.title) {
case 'Футбол':
titleImg = require('../res/soccer.png');
textColor = '#76a963';
break;
case 'Баскетбол':
titleImg = require('../res/basketball.png');
textColor = '#d47b19';
break;
case 'Хоккей':
titleImg = require('../res/hockey.png');
textColor = '#3381d0';
break;
case 'Теннис':
titleImg = require('../res/tennis.png');
textColor = '#d6b031';
break;
}
In this snippet I change variables titleImg and textColor depending of the prop. I have put this snippet directly in render() method.

I have found that a dynamic path for require() works when it starts with a static string. For example require("./" + path) works, whereas require(path) doesn't.

Simple to dynamic images (using require)
Example array(into state)
this.state={
newimage: require('../../../src/assets/group/kids_room.png'),
randomImages=[
{
image:require('../../../src/assets/group/kids_room.png')
},
{
image:require('../../../src/assets/group/kids_room2.png')
}
,
{
image:require('../../../src/assets/group/kids_room3.png')
}
]
}
Trigger image( like when press button(i select image random number betwenn 0-2))
let setImage=>(){
this.setState({newimage:this.state.randomImages[Math.floor(Math.random() * 3)];
})
}
view
<Image
style={{ width: 30, height: 30 ,zIndex: 500 }}
source={this.state.newimage}
/>

Hey lads I rounded another way to require It's ugly but works. Images dynamically. Instead of storing your URL in the state you store the entire JSX. For an example:
state = {
image: []
};
Instead of
let imageURL = `'../assets/myImage.png'`
this.state.image = imageURL
You use
let greatImage = (<Image source={require(../assets/myImage.png)}></Image>)
this.state.image = greatImage
To render in the JSX
{this.state.image}
You can style your image in the variable too. I had to use some if statements to render some images dynamically and after break my head for 2 hours this was the way that solved my problem. Like I said It's ugly and probably wrong.

Are you using a module bundler like webpack?
If so, you can try require.ensure()
See: https://webpack.js.org/guides/code-splitting/#dynamic-imports

Reading through the docs, I've found a working answer and I'm able to use dynamic images, in the docs they refer to it as Network Images here
https://facebook.github.io/react-native/docs/images#network-images
Not sure if this can be applied to other file types, but as they list require with non image types
You would need to use the uri: call
data = {uri: urlName}
For me I got images working dynamically with this
<Image source={{uri: image}} />

Try the solution mentioned in this thread for Android. This solves the issue but unfortunately, it's only for android.
But make sure to run react-native run-android after every update. Else, the added images won't appear in the app.

This seems to work :
const {
messages,
} = require(`../${dynamicPath}/messages.specific`);

Related

React variable component name - all lower case?

I am trying to render icons that are set in the back end for each service on the page. The data comes from an api and includes the correct names for the icons. I am importing the needed icons from react-icons before hand...
I tried it this way:
//Services and icons
var serviceICON = this.state.services.map(service => {
let Icon = service.Icon
return <tr><td><Icon /></td><td> {service.serviceName}</td></tr>
});
This almost works - the only problem is that the icons are not rendered. Instead the html looks like this:
<fabed></fabed>
I don't understand why this happens. The api delivers the correct name (=> FaBed), so why is this rendering as all lower case?
Thanks a lot for your help in advance!
Edit:
The complete array for one of the services would look like this:
id: 2
serviceName: "Hotel"
trip: 6
Icon: "FaBed"
created_at: "2020-07-08T06:45:02.239Z"
updated_at: "2020-07-22T07:52:05.066Z"
I am mapping through each of these and try to output the code above. As you can see "Icon" comes with the correct spelling. So I don't understand why it is rendered in all lower case...
Basically what is happening here is Icon is just a string so the react framework treats it differently.
html tags vs react components
<Icon />
is converted to
React.createElement(Icon, {});
Choosing the Type at Runtime
You'll notice here the key is using a map to match a string value with an actual React Component. For your case you'll need to map service.Icon to the relevant (imported) Icon Component.
import FaBed from '...';
...
const icons = {
...
"FaBed": FaBed,
...
};
...
var serviceICON = this.state.services.map(service => {
let Icon = icons[service.Icon];
return <tr><td><Icon /></td><td> {service.serviceName}</td></tr>
});

React native require Image dynamically on state update with many possible options

I have a react-native-modal-dropdown with multiple options. When the user selects an option, changeImage() is triggered and the image path updates from over 150 possible images.
However the following solution doens't update the Image component because react native doesn't allow dynamic require.
const [imagePath,setImagePath] = useState('defaultImg')
// newImg, is a dynamic string which changes with user input
// "assets/images" folder has over 150 images
const changeImage = (newImg) {
// if valid path, change path
setImagePath(newImage)
}
return(
<Image
style={styles.imageStyle}
resizeMode="center"
source={require('../assets/images/' + imagePath + '.png')}
/>)
This solution is not scalable, neither is this.
I've seen some solutions using json and helper methods but I couldn't get them to work.
I've figured it out using an "images.js" helper file.
In "App.js" I'm updating the image path depending on a "target" state value.
import {getImage} from './images';
// Image is updated based on this value
const [target,setTarget] = useState("default");
// Default image path
const [imagePath,setImagePath] = useState({path: getImage('default')})
useEffect(() => {
setImagePath({
path: getImage(target);
});
},[target])
return(
<View>
<Image source={imagePath.path} />
</View>
)
And here's "images.js" which is in the same path as a folder called "pictures":
export function getImage(input) {
switch (input) {
case "value_1": return require('./pictures/Picture1.png');
case "value_2": return require('./pictures/Picture2.png');
case "value_3": return require('./pictures/Picture3.png');
...
case "value_149": return require('./pictures/Picture149.png');
case "value_150": return require('./pictures/Picture150.png');
case "default": return require('./pictures/placeholder.png');
}
}
Creating the above function can be tedious so I created a python script that outputs the switch result programmatically to an output file and I simply copied pasted the output into this file.

Loading mesh(OBJ+MTL+JPG) with react-three-fiber, Texture is not working

In my react app, I am trying to make a view page which shows 3d-mesh exported from pix4d. This mesh consists of three types of files, (.obj, .mtl, .jpg) pix4d.com.
I am new, with react-three-fiber, which I suppose is best way to achieve my solution to my problem.
Below is whole code of react component used to load and render 3D-Model.
code
Thanks in Advance!
I want to understand how to attach texture & material to my obj rendered model.
I was looking for this answer for a couple of weeks, finally I've found a way to make it work.
Material loader is not resolving by itself the import of remote files (it does on web, not in mobile, maybe in the future it will). So, I'm creating material and assigning it images by hand.
Something like this:
import { TextureLoader } from 'expo-three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
// useThree is used to get scene reference
import { useThree } from 'react-three-fiber';
const textureLoader = new TextureLoader();
const mapImage = textureLoader.load(require('path/to/image1.png'))
const normalMapImage = textureLoader.load(require('path/to/image2.png'))
Note that TextureLoader from expo-three can handle the file resource returned from require()
const loaderObj = new OBJLoader();
const loaderMtl = new MTLLoader();
export default props => {
const { scene } = useThree();
loaderMtl.load(
'https://url_to/material.mtl',
mtl => {
mtl.preload();
loaderObj.setMaterials(mtl);
loaderObj.load(
'https://url_to/model.obj',
obj => {
// simple logic for an obj with single child
obj.children[0].material.map = mapImage;
obj.children[0].material.normalMap = normalMapImage;
scene.add(obj)
}
)
}
)
return null;
}
This is my first successful attempt to render an obj with mtl including a map and a normal map, so since it works, we can keep updating the code for improvements.
Another way to load model with texture is by specifying the path where your texture has been stored. In one case, mtl + obj + texture files are stored in your react project's directory 'public/models/'. So you can specify the path by calling setPath() function prior loading your material or object file and it should load your texture on the material. You may also want to make sure that in ./mtl file the texture name is correct. It should be called following by map_Kd in .mtl file.
const Model = () => {
const materialLoader = new MTLLoader().setPath('./model/').load(MaterialFile);
const objLoader = new OBJLoader().setMaterials(materialLoader).load(OojectFile);
return <primitive object={objLoader} />;
};

React dynamic import files with variable

I am trying to load files with variables
my current implementation:
const map = {
google: import('./svg/google.svg'),
microsoft: import('./svg/microsoft.svg')
}
const Image = ({ name }) => {
//assume map[name] always exists
const Component = map[name];
return (
<Component />
)
}
the above snippet works perfectly. now the requirement is to add another 400 companies.
Well, i don't want to create & maintain a map with 400 keys.
so I am wondering if there is any better to do that? i.e. load the file with variable
something like
import(`./svg/${name}.svg`)

React and Three.js MTLLoader "material.onBeforeCompile is undefined"

I'm trying to build a simple object-viewer in React with Meteor that can import .obj and .mtl Files using the following npm modules:
three(0.87.1)
react(15.6.1)
three-obj-loader(1.1.3)
three-mtl-loader(1.0.1)
So far i have managed to display an object using the OBJLoader.
But when i try to render an object after applying a texture with MTLLoader, i get this error from console:
Uncaught TypeError: Cannot read property 'toString' of undefined
at WebGLPrograms.getProgramCode (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:50707)
at initMaterial (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54628)
at setProgram (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54820)
at WebGLRenderer.renderBufferDirect (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:53883)
at renderObject (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54613)
at renderObjects (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54586)
at WebGLRenderer.render (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54350)
at WebGlDisplay.renderScene (WebGlDisplay.jsx:86)
at onClick (WebGlDisplay.jsx:90)
at HTMLUnknownElement.boundFunc (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:8794)
Cause: material.onBeforeCompile in getProgramCode is undefined
My code looks like this:
import React, { Component } from 'react'
import THREE from 'three'
const MTLLoader = require('three-mtl-loader');
const OBJLoader = require('three-obj-loader')(THREE);
export default class WebGlDisplay extends Component {
constructor(props) {
super(props)
}
//init canvas
init(){
const width = this.mount.clientWidth;
const height = this.mount.clientHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setClearColor('#000000', 0.2);
renderer.setSize(width, height);
camera.position.set(3,4,6);
camera.lookAt(new THREE.Vector3());
this.scene = scene;
this.camera = camera;
this.renderer = renderer;
this.mount.appendChild(this.renderer.domElement);
}
//load & render object
drawOBJ(){
const mtlLoader = new MTLLoader();
let onProgress = function(e){console.log("rendering:" + e)};
let onError = function(e){console.log("error:" + e)};
mtlLoader.load("eagle.mtl", materials => {
materials.preload();
// OBJ Loader
const objLoader = new THREE.OBJLoader();
this.materials = materials;
objLoader.setMaterials(materials);
objLoader.load("eagle.obj", object => {
this.object = object;
this.scene.add(object);
}, onProgress, onError);
}, onProgress,onError);
this.renderScene();
}
componentDidMount() {
this.init();
this.drawOBJ();
}
renderScene() {
this.renderer.render(this.scene, this.camera)
}
render() {
return (
<div onClick={(e) => this.renderScene()}
style={{ width: '800px', height: '600px' }}
ref={(mount) => { this.mount = mount }}
/>
)
}
}
Does anyone have an idea why i get this error?
I've tried to use different .obj- and .mtl-files, but the error remains (whenever i try to call renderScene()).
By any chance, could it be a problem with the module versions, or maybe some timing problems while loading?
Any help would be appreciated.
The problem seems to be that the three-mtl-loader NPM package is referencing an outdated three.js version in it's package.json, so even though you are using the latest version of three, the plugin isn't!
Obviously this isn't a viable long-term fix, but I changed the version for three in node_modules/three-mtl-loader/package.json to 0.87.1 and deleted the directory node_modules/three-mtl-loader/node_modules for good measure and ran my example and it worked straight away, textures and all.
Clearly the plugin needs to be updated. I also saw at least one functional difference between the source in the plugin and in the three examples folder ('Tr' vs 'tr' in a case statement), and it doesn't follow the same initialization behavior as other loader plugins (specifically it isn't initialized by calling require("three-mtl-loader")(THREE)), so there's a bit of work to get it ship-shape.
Alternately, it appears that the author has updated the version number to 0.86.0 in their repo (which is high enough), just hasn't done a deploy to NPM. So, if you feel brave, you can just change your package.json to have the line
"dependencies": {
...
"three-mtl-loader": "git+https://git#github.com/nascherman/three-mtl-loader.git",
...
}
As a workaround, what i ended up doing is to get a local copy of the newest MTLLoader Version and slightly modifing it, as it seems to be a problem with the version pointed out by #user1691694.
In case somebody needs this here my way of importing it:
In the MTLLoader file, add the following line at the top:
import THREE from 'three';
and at the bottom:
module.exports.default = THREE.MTLLoader
Use it like in the drawOBJ function in the question post and import it in the target file like this:
import MTLLoader from './MTLLoader.js';

Resources