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>
});
I'm using react-virtualized for a lengthy (1000+) list of items to select from. And I'm trying to set up an end to end test that requires clicking on one of the elements that are not currently rendered.
Ordinarily, I'd simply use something like:
await t.click(
ReactSelector('ListPage')
.findReact('ListItem')
.nth(873) // or .withText(...) or .withProps(...)
)
But because only a small subset of the ListItems are rendered, TestCafe fails to find the desired element.
I've been trying to figure out how to use TestCafe's ClientFunction to scroll the list container so the desired ListItem is rendered.
However, I'm running into a few issues:
Is there a way to share a Selector into the ClientFunction and modify the DOM element's scrollTop? Or do I have to re-query the element via the DOM directly?
Due to the ListItems being different heights, the scroll position is not a simple calculation of index x item height. How can I keep updating/scrolling within this function until the desired Selector is visible?
Is there a way to share a Selector into the ClientFunction and modify the DOM element's scrollTop?
There is a way to put Selector into the Client Function. Please refer to this example in the TestCafe documentation.
How can I keep updating/scrolling within this function until the desired Selector is visible?
You can use the TestCafe exist property to check if the element is rendered or not. The following example demonstrates the approach:
import { Selector } from 'testcafe';
fixture`Getting Started`.page('https://bvaughn.github.io/react-virtualized/#/components/List')
test('Test 1', async t => {
const dynamicRowHeightsInput = Selector('._1oXCrgdVudv-QMFo7eQCLb');
const listItem = Selector('._113CIjCFcgg_BK6pEtLzCZ');
const targetItem = listItem.withExactText('Tisha Wurster');
await t.click(dynamicRowHeightsInput);
while (!await targetItem.exists) {
const currentLastRenderdItemIndex = await listItem.count -1;
const currentLastRenderdItemText = await listItem.nth(currentLastRenderdItemIndex).textContent;
const currentLastRenderdItem = await listItem.withExactText(currentLastRenderdItemText);
await t.hover(currentLastRenderdItem);
}
await t
.hover(targetItem)
.click(targetItem);
await t.debug();
});
To scroll the list container I used the hover action with a last rendered listItem as a target element.
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`);
An example of what I am talking about is here: https://detrum-replication.herokuapp.com/
My problem can be seen when the hovering diamond in the bottom left is clicked. I have both the gradient and background rendered conditionally according to a value in the state. It just cycles from 1-4 and changes the class of the divs according to the number of the state. The only thing that I don't like is the split second flash when the diamond is clicked. Once the images have been loaded and cycled through it no longer flashes so I assume the images have been cached. Is there a way to precache the images I will be using for the background using React? Did I go about this in the wrong way to achieve what I am trying to do? I have tried using a few precachers for React with no luck. Some of my code is as follows:
constructor(props) {
super(props);
this.state = {
bg: 1
}
}
changeSeason = () => {
let current = this.state.bg;
if (current >= 4){
current = 1;
} else {
current++;
}
this.setState(() => ({bg : current}));
localStorage.setItem("bg", current);
};
render() {
return (
<div className={`gradient${this.state.bg}`}>
<div className={`background${this.state.bg}`}></div>
<div className="diamond" onClick={this.changeSeason}><div className="diamond__shadow-bottom"></div><div className="diamond__shadow-right"></div></div>
</div>
)
}
Thanks for taking the time to help me.
You can preload assets on your app when you know for sure you would be needing them. The browser doesn't wait for your javascript/css to parse and know that you need them, thereby preventing the flicker.
You need to add them to your index.html like this
<link rel="preload" href="bg-image.png" as="image">
Look at the MDN docs for reference
I have built a Quill text editor in ReactJS with React-Quill. I would like to wrap the generated text in the text editor with a Div tag instead of a P tag. Is this possible?
Overview of my use: When text is generated/edited in the Quill editor it is duplicated in another div on another part of the page. Our use of this editor goes back a few years, and thus was built on an older version of Quill, when the text was generated in Div's. We recently upgraded to Quill 1.0 and when the text is generated in P tags it creates unwanted styles that we can't remove. The simplest and least hacky solution would be to generate div's instead of p tags, but I have no idea if that's even possible.
Does anyone know more about this?
Thank you.
You can just change the tagName of the default Block:
var Block = Quill.import('blots/block');
Block.tagName = 'div';
Quill.register(Block);
Working example: https://codepen.io/anon/pen/brgvPR
This worked for me. I am using next.js
import dynamic from 'next/dynamic';
const ReactQuill = dynamic(async () => {
const ReactQuill = await import('react-quill');
const { Quill } = ReactQuill.default;
const Block = Quill.import('blots/block');
Block.tagName = 'div';
Quill.register(Block);
return ReactQuill;
}, { ssr: false });