Setting span labels with a loop - reactjs

I pulled data with request and now I'm trying to present each result on my site. I get an array with genres each time, so I need some ideas on how I could use loops in JSX in order to put those spans in result div that already has images, heading and stuff.
setGenres = () => {
let data = this.props.data.genres;
let labelsText = '';
for (let i = 0; i < data.length; i++) {
labelsText += <span className='genre'>data[i]</span>
}
return (labelsText);
}
<div className='result-info'>
<h4>{this.props.data.name}</h4>
{this.setGenres()}
</div>
Back when I was using vanilla JS, I could use string and put it via innerHTML, but now I have no idea what to do. It just returns [Object object] on my site.

You can use map to iterate over the array and return another result. So it's possible to return a portion of JSX which will be rendered inside your div:
<div className='result-info'>
<h4>{this.props.data.name}</h4>
{this.props.data.genres.map((genre,idx)=>(
<span key={idx} className='genre'>{genre}</span>
)}
</div>

JSX generates element classes for React to use. React allows rendering of various things, including arrays of React classes. In your scenario setGenres needs to change to the following.
setGenres = () => {
const data = this.props.data.genres;
const spans = data.map(genre => (<span key={genre} className='genre'>{genre}</span>));
return spans;
}
As noted by another user, I added the key prop as react will complain otherwise. It's best to set the key to something guaranteed to be unique, rather than just an index.
You can read more about array mapping here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
You can read more about lists in react here - https://reactjs.org/docs/lists-and-keys.html

Related

How do I dynamically render new component after a change in previous component?

I want to create a CMS in React where a user can enter N amount of pictures.
To do this, i created a FileUploader component. I can track when the user interacts with the component and read the file from this component.
I want to make it so there are N+1 FileUploaders rendered for N images in this.state.images
So if I have 3 images in the array, I want to render 4 FileUploaders
How do I do this with React?
Why not just create a loop that renders the FileUploaders? You already has the number of images. Therefore, it should be easy to just do
for (let i = 0; i < this.state.images.length + 1; i++)
Since you haven't presented some code to work with.. I just create some example code for you
const MyComponent = () => {
const [images, setImages] = React.useState(["some stuff", "some stuff"]);
return (
<>
{[...images, undefined].map(image => <FileUploaders />)}
</>
);
}
Notice that i add undefined at the end of the array, so we have one more entry to loop through. If you plan to use the image to something. You can just check if the image is undefined or not.

Why does Object.keys(this.refs) not return all keys?

Hi,
so I've redacted some sensitive information from the screen shot, but you can see enough to see my problem.
Now, I'm trying to build the UI for a site that gets data from a weather station.
I'm trying to use react-google-maps' InfoBox, which disables mouse events by default.
It seems that to enable mouse events, you must wait until the DOM is loaded, and then add the event handlers.
react-google-maps' InfoBox fires an onDomReady event (perhaps even upon adding more divs) but seems to never fire an onContentChanged event (I've looked in the node_modules code).
The content I'm putting in the InfoBox is basically a div with a string ref for each type of weather data. Sometimes there comes along a new type of weather data so I want to put that in also, and have the ref be available / usable.
However, immediately after the new divs have been added (and the DOM has been updated to show them), when I try to console log the DOM nodes (the refs refer to the nodes because they are divs and not a custom built component) the latest added ones are undefined.
They do become a div (not undefined) a few renders later.
I've contemplated that this may be because
1) the DOM is not being updated before I'm trying to access the refs, but indeed the UI shows the new divs,
2) string refs are deprecated (React 16.5),
but they work for the divs in comonentDidMount and eventually for new divs in componentDidUpdate,
3) executing the code within the return value of render may be run asynchronously with componentDidMount, but I also tried setTimeout with 3000 ms to the same effect,
4) of something to do with enumerable properties, but getOwnProperties behaves the same way.
In the end I decided I'll console log this.refs and Object.keys(this.refs) within the same few lines of code (shown in the screen shot), and you can see that within one console log statement (where Object.keys was used in the previous line) that while this.refs is an object with 8 keys, the two most recently added refs don't appear in Object.keys(this.refs).
This is probably a super complex interaction between react-google-maps' InfoBox, React's refs, and JavaScript's Object.keys, but it seems like it should be simple and confuses me to a loss.
Can anyone shed some light on why this might be happening??
The code looks something alike:
class SensorInfoWindow extends React.Component {
handleIconClick = () => {
// do stuff here
}
componentDidMount() {
this.addClickHandlers();
}
componentDidUpdate() {
this.addClickHandlers();
}
addClickHandlers = () => {
const keys = Object.keys(this.refs);
for(let i=0; i<keys.length; i++) {
const key = keys[i];
let element = this.refs[key];
if (element !== undefined)
element.addEventListener('click', this.handleIconClick);
}
}
render() {
const { thissensor, allsensors } = this.props;
let divsToAddHandlersTo = [];
const sensorkeys = Object.keys(allsensors);
for (let i=0; i<sensorkeys.length; i++) {
divsToAddHandlersTo.push(
<div
ref={'stringref' + i}
/>
{/* children here, using InfoBox */}
</div>
);
}
return (
<div>
{divsToAddHandlersTo}
</div>
);
}
}
This is, in essence, the component.

for loop not working in render function reactjs

m looping using for in render function
its giving syntax error can we not use this way in react
following is the code
render(){
return(
<table>
for(var i=0;i<this.props.TRlength;i++)
<TRcomponent TDlength={this.state.tdLength} />
</table>
)
}
error it throws
`/src/Table.js
Syntax error: D:/my-app1/src/Table.js: Unexpected token (17:50)
<table>
for(var i=0;i<this.props.TRlength;i++)//error here
^
<TRcomponent TDlength={this.state.tdLength} />
`
any help is appreciated
React doesn't play well with for loops in the render. You cannot create children for a parent that does not exist yet. You need to create the children first.
This article goes into detail about this issue.
You can store it in a variable and then use it in jsx
render() {
var rows = [];
for (var i=0;i<this.props.TRlength;i++) {
// note: we add a key prop here to allow react to uniquely identify each
// element in this array. see: https://reactjs.org/docs/lists-and-keys.html
rows.push(<TRcomponent TDlength={this.state.tdLength} key={i} />);
}
return < table >{rows}</table>;
}

How can you generate predictable react keys for items without unique ids?

At times, I map over collections without unique ids. In that case, react logs this warning to the console: Each child in an array or iterator should have a unique "key" prop. To solve this issue, I created the following es6 module.
const key = {
count: 0,
getKey: () => {
key.count += 1;
return key.count;
},
};
export default key;
Now, I import the key module throughout my React application and call its getKey method when I need a unique key. But, I don't feel like the keys this generates are predictable like React advises. While the first render might map a collection with keys 1-10 predictably, any render afterwards will generate a new key for that element. Using a package like UUID will have the same effect will it not? What is the proper way to generate predictable React keys? Am I misunderstanding what they mean by predictable? Thanks!
Generating the keys before rendering the component is the best way to end up with predictable ones.
In an ideal world you will be working with lists of objects that already have their own unique id and in those cases it's best to use those ids as keys.
If you were rendering a list of numbers that might include duplicates, you could give them each a fixed key before passing them to a component to render.
let numbers = [1, 1, 2, 3];
let numbersWithKeys = numbers.map(x => {
return { value: x, key: key.getKey() };
});
function Numbers({ numbers }) {
return (
<ul>
{numbers.map(number => <li key={number.key}>{number.value}</li>)}
</ul>
)
}
render(
<Numbers numbers={numbersWithKeys} />
);
Then every single time your component renders it can safely use the same key for each number. You can imagine this component rendering the following virtual tree:
<ul>
<li key={1}>1</li>
<li key={2}>1</li>
<li key={3}>2</li>
<li key={4}>3</li>
</ul>
It would be possible for the <Numbers> component to reverse the list and you'd still end up with the same key for each item. That's what React means by predictable.
React can make some performance shortcuts when you change the order of a list by moving the rendered elements around, rather than re-rendering the entire thing, but if you're generating your keys inside your render method, then it sees a brand new list each time and has to re-render it all.
Here are a couple of things to consider:
Unique: Keys must be unique, but for the current array only.
Predictable: Keys must consistently map to the same element.
Keys do not have to be ridiculously convoluted identifiers.
What's predictable? If we start with (index:key): 1:1,2:2,3:3
and we remove the element on index 2, we should be left with: 1:1,2:3. The item at index 2 with key 2 has been removed, and the item on index 3 has moved to index 2, but retained its key 3.
The simplest way is of course to use a unique property in your data, like your database id (primary key), but that's not always possible. Let's say you want to create a dynamic form with input fields that can be added or removed. The input field elements may look exactly the same, except for a unique identifier which is dynamically assigned. The simplest way is of course using the map index, but that's not predictable, since it will change if you remove the second of three fields, for example.
What are our options?
One solution is to track your elements in state.
class MyComponent extends Component {
constructor() {
super();
this.state = {
myArr: []
};
}
_addHandler = el => {
const arr = this.state[el];
const length = arr.length;
const lastId = length > 0 ? arr[length - 1] : 0;
this.setState({ [el]: [...arr, lastId + 1] });
};
_removeHandler = (el, i) => {
const newItems = [
...this.state[el].slice(0, i),
...this.state[el].slice(i + 1)
];
this.setState({ [el]: newItems });
};
render() {
const myList = this.state.myArr;
return (
<div>
{myList.map((el, i) => (
<p>
{this.state.myArr[i]}{" "}
<span onClick={() => this._removeHandler("myArr", i)}>
remove
</span>
</p>
))}
<p onClick={() => this._addHandler("myArr")}>Add One</p>
</div>
);
}
}
You could write a simple wrapper component to track elements in an array using this concept, if it's something you're going to be doing a lot.
To assign the initial keys you could do the following in componentDidMount()
componentDidMount() {
const {someArr} = this.props;
// use the index to map the initial key values
// after which use state to track the key pairings
const newIds = someArr.map((el,i) => i+1);
this.setState({myArr:newIds});
}
you can use any data specifically related to the item... or (not recommended) for random id you can use new Date().getTime()
key is used for reusing components... it's important to give the same key for the same item
[].map(item, index) => <Item key={'this_list_specific_name' + item.id} />

React- onClick styling of currentTarget

I am trying to build a simple dynamically updated, interactive list that styles each <li></li> according to the css rules of a .clicked class, when you click on them.
The app is composed of two components, a parent and a child and the code in question is the following (taken from the child):
handleClick(e) {
document.getElementById(e.currentTarget.id).setAttribute("class","clicked");
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x,i)=>{ return (<li id={i} key={i} className={i%2==0 ? "white" : "grey"}
onClick={this.handleClick}>{x}</li>); })
return (
<div>
<ul id="ul">{ pro }</ul>
</div>
What is happening here is basically that the parent is passing to the child a sentences prop (an array of sentences that will form the basis for the formation of a dynamic list).
The controversial part is me using DOM manipulation in the form of document.getElementById(e.currentTarget.id).setAttribute("class","two");
in order to change the class of the dynamically created html from jsx.
The code above works, however it does not feel as best practice. The whole advantage in using react is to use virtual dom and optimize the way the DOM is updated.
My questions are the following:
1) Am I right to feel this way? (that my solution is not best practice?)
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
If you know this question to be a duplicate, please leave a comment and I ll remove it.
1) Am I right to feel this way? (that my solution is not best practice?)
It is correct to assume that this is not an ideal approach, manipulating the DOM via vanilla js in React has its place (Example Use Cases) but should not be done unless absolutely necessary. Also, it is not ideal to use the index from Array.prototype.map as the key on your components as if they change order it can cause confusion for React as the keys would map differently in that case.
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
You should make use of the component state. If you want each clicked element to maintain the clicked class then make a piece of state that caches the elements that have already recieved the clicked class. if only the most recently clicked element gets the clicked class then simply cache an identifier to the appropriate element in the state. You could also use refs for this purpose though the overusage of them is somewhat discouraged by facebook.
Here is a quick snipped that will toggle the click class on each <li>
class Test extends Component {
constructor() {
super();
this.state = {
clicked: {}
};
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x, i) => {
const color_class = i % 2 === 0 ? "white" : "grey";
const clicked_class = this.state.clicked[i] === true ? "clicked" : "";
let clicked = Object.assign({}, this.state.clicked); // Dont mutate state!!!
return (
<li
id={i}
key={i}
className={`${color_class} ${clicked_class}`}
onClick={e => {
if (clicked.hasOwnProperty(i)) {
delete clicked[i];
} else {
clicked[i] = true;
}
this.setState({ clicked });
}}
>
{x}
</li>
);
});
return (
<div>
<ul id="ul">
{pro}
</ul>
</div>
);
}
}

Resources