React dynamic import files with variable - reactjs

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`)

Related

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} />;
};

ReactJS - Async Dynamic Component Loading

Situation
I receive json from a cms that describes the content that needs to display on any given page. This project has 50+ components so rather than require all of them on every page I'd rather cherry pick them as needed.
Question
How can I
Make sure all components are available for import (I assume this requires some webpack trickery)
When converting the json's content node to jsx, making sure that any component described is rendered out.
Current Thoughts
I can loop through the raw jsx and collect all the tags for a given page then attempt a load for each tag via something like
const name = iteration.tagName;
dynCmps[name] = someAsynchronousLoad(path + name);
Then dispatch a redux event when loading is complete to kick off a fresh render of the page.
As for converting raw text content to react js I'm using ReactHtmlParser
best resources so far
Dynamic loading of react components
http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack/
This had me stumped for a couple of days. After chatting with a colleague about it for some time it was decided that the amount of work it would take to offload the performance hit of loading all the components upfront is not work it for our scenario of 30-50 components.
Lazy loading CAN BE used but I decided against it as the extra 10ms of loading (if that) isn't going to be noticeable at all.
import SomeComponent from "./SomeComponent.js"
const spoofedComponents = {
SomeComponent: <SomeComponent />
}
const replaceFunc = (attribs, children) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
// lower case is important here because react converts everything to lower case during text-to-html conversion - only react components can be camel case whereas html is pascal case.
if(attribs.name === key.toLowerCase()) {
return spoofedComponents[key];
}
}
return <p>unknown component</p>
}
...
//inside render
const raw = "<SomeComponent><SomeComponent />"
// it's VERY important that you do NOT use self-closing tags otherwise your renders will be incomplete.
{parse(raw, {
replace: replaceFunc
})}
In my case I have 30+ components imported and mapped to my spoofedComponents constant. It's a bit of a nuissance but this is necessary as react needs to know everything about a given situation so that the virtual dom can do what it is supposed to - save on display performance. The pros are that now a non-developer (editor) can build a layout using a WYSIWYG and have it display using components that a developer made.
Cheers!
Edit
I'm still stuck on adding customized props & children.
Edit
Basic props are working with
const spoofedComponents = {
SomeComponent: (opts) => {
let s = {};
if(opts.attribs.style)
s = JSON.parse(opts.attribs.style);
if(opts.attribs.classname) {
opts.attribs.className = opts.attribs.classname;
delete opts.attribs.classname;
}
return <APIRequest {...opts.attribs} style={s}>{opts.children[0].data}</APIRequest>
}
}
...
const replaceFunc = (opts) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
if(opts.name === key.toLowerCase()) {
const cmp = spoofedComponents[key](opts);
return cmp;
}
}
return <p>unknown component</p>
}
Now to figure out how to add child components dynamically..
EDIT
This is working well enough that I'm going to leave it as is. Here is the updated replaceFunc
const replaceFunc = (obj) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
if(obj.name === key.toLowerCase()) {
if(obj.attribs.style)
obj.attribs.style = JSON.parse(obj.attribs.style);
if(obj.attribs.classname) {
obj.attribs.className = obj.attribs.classname;
delete obj.attribs.classname;
}
return React.createElement(spoofedComponents[key], obj.attribs, obj.children[0].data)
}
}
return obj; //<p>unknown component</p>
}

Not allow to require by using variable? [duplicate]

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`);

How to access props from an externam js file - ReactJS

I need to access props in an external JavaScript file.
I have a JavaScript file which hold the static values. Like
export const SEARCH = {
[Data.NAME]: 'search-default',
[Data.LABEL]: 'Search',
[Data.TYPE]: Types.INPUT_SEARCH,
[InputData.PLACEHOLDER]: 'Search'
};
export const SEARCH_MAP = {
[Data.NAME]: 'search-map',
[Data.LABEL]: 'Search any address in the world',
[Data.TYPE]: Types.INPUT_SEARCH,
[InputData.PLACEHOLDER]: 'Search any address in the world'
};
I need to access the props at run time to set values dynamically. Basically I am trying to achieve multi-language implementation.
Is there any way to access the props here ?
as u r exporting an object use . operator and access using that particular key
for example
const search = require(); //import file
name = search.[DATA.NAME]
like this u can access values in other js file

Resources