ForEach inside of a map Javascript React not showing up [duplicate] - javascript

This question already has answers here:
How to have nested loops with map in JSX?
(4 answers)
Closed 9 months ago.
Anyone know why this isn't generating any components?
sArray is a 2d array with a structure like a box [[1,2],[4,4]], but it's 5x5
{
sArray.map( (array) => {
console.log('inthemap');
array.forEach(element => {
console.log('intheEach');
return (
<div>
<S ticketValue={element}> </S>
</div>
)
});
})
}
When I run the page the Console logs the InTheMap and then the InTheEach 5 times, and it does this 5 times. Meaning I'm making 25 S components, but they do not show up.
When I remove the forEach, the S component does show up.

There are two issues I see above:
You are missing a return statement for the inner loop
You should be using map instead of forEach since you are returning
{
sArray.map((array) => {
// you forget to return this
return array.map(element => {
return (
<div>
<S ticketValue={element}> </S>
</div>
)
})
})
}
Also quick note that this has a runtime complexity of O(n^2) which may be ok for smaller arrays, but will slow performance exponentially as they grow in size.
You may want to move this from outside the render method and compute with useMemo to prevent unnecessary re-renders:
https://reactjs.org/docs/hooks-reference.html#usememo
// compute this value only once (or add dependencies for when it should update)
const myExpensiveRender = useMemo(() => {
return sArray.map((array) => {
return array.map(element => {
return (
<div>
<S ticketValue={element}> </S>
</div>
)
}
},[])
return (
<>
{myExpensiveRender}
</>
)

Related

How to loop React Elements for a specific amount of times?

I am trying to make a looper component so that I can loop any of its children for a specific amount of time.
How can I do that?
// Looper
function Looper({ children, array }) {
return (
<div>
{array.map((item) => (
<div key={item}>{children}</div>
))}
</div>
);
}
// It works, but it needs a dummy array that I don't want.
<Looper array={[1, 2, 3, 4, 5]}>
<span>Hello Guys..</span>
</Looper>
You can create an array of incrementing numbers on the fly using [...Array(times).keys()], like so:
// Looper
function Looper({ children, times }) {
const keys = [...Array(times).keys()];
return (
<div>
{keys.map((item) => (
<div key={item}>{children}</div>
))}
</div>
);
}
<Looper times={5}>
<span>Hello Guys..</span>
</Looper>
replace your map function with a for loop and pass a count i:e number of times you want the looper to render, and use that index as a key for the child divs then push them into an array. Finally, return that array of child divs. Check out this link if you want to go with this approach Loop inside React JSX

Accessing nodes returns null in react

I am trying to render a list of searched users returned from server and add when clicked on any of the list items, get the value of inside the node component.
First I fetch the users
useEffect(() => {
if (searchText.length === 0) {
setSearchedUsers([])
return
}
fetch(--users--)
}).then((res) => res.json())
.then((users) => {
setSearchedUsers(users)
})
}, [searchText])
then render
const searchedNodes = useRef([])
function renderWithRefs() {
return (searchedUsers.map((user, i) => (
<div className={`searched-user-div ${i}`} ref={(div) => searchedNodes.current[i] = div} key={i} onClick={(e) => showUserData(e)}>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
)))
}
function showUserData(e) {
let ref = searchedNodes.current[parseInt(e.target.className.split(" ")[1])]
let Name = ref.querySelector(".searchedUser-Name").textContent
let age = ref.querySelector(".searchedUser-age").textContent.slice(1)
....
}
when clicked I get Error: cannot read property 'querySelector' of undefined
Directly querying the dom in React is a very bad pattern. You should not do it unless you have no choice ( for example you need to query the size of an item ).
In your case, you could do something way easier like this:
<div className={`searched-user-div ${i}`} key={i} onClick={(e) => showUserData(user)}>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
You then don't have to query the DOM.
First,
The use of array index as a key is not recommended
It's a bad idea to use the array index since it doesn't uniquely
identify your elements. In cases where the array is sorted or an
element is added to the beginning of the array, the index will be
changed even though the element representing that index may be the
same. This results in unnecessary renders.
function renderWithRefs() {
return (searchedUsers.map((user, i) => (
<div className={`searched-user-div`} ref={(div) => searchedNodes.current[i] = div} key={JSON.Stringlify(user)} onClick={() => showUserData(user)} onKeyPress={() => showUserData(user)} tabIndex={0} role="button">
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
)))
}
function showUserData(user) {
//here you can show all user data
<>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
<p className="searchedUser-LastName">{user.LastName}</p>
<p className="searchedUser-tel">{user.tel}</p>
....
</>
....
}
You can see I added a few props to your div all because of no-static-element-interactions
Static HTML elements do not have semantic meaning. This is clear in
the case of and . It is less so clear in the case of
elements that seem semantic, but that do not have a semantic mapping
in the accessibility layer. For example , , ,
, , and -- to name a few -- have no
semantic layer mapping. They are as void of meaning as .

Cannot map value of object seen in console

My idea is to click on any of the buttons on the left navbar and whenever the name of the clicked button in the logos object matches any of the names in the items object within projects, then display those objects.
When I click on any of the buttons on the left, I transform that object's active property to true within the logos object. After I've filtered the values, I can see all of the correct values in the console, but I can't loop through them - with a for loop or a map. Oddly enough, when I write filteredValues[0] I am able to output that data to the screen but since I want multiple outputs for some of these clicked values, this is not an option. Any help is appreciated. Thank you!
These are the items that I can't loop through but am getting back when I console log them
These are my projects
These are my logos
const Homepage = () => {
const {state} = useContext(Context)
const {projects,logos} = state;
return (
<>
<div className="container">
<Language />
<div className="homepage">
{logos.map(logo => {
let filteredValues;
if(logo.active == true){
filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name))
filteredValues.map((val) =>{
console.log(val)
return(
<div>{val.title}</div>
)
}) //end of filteredValues.map
} //end of if
}) // end of logos.map
}
</div>
</div>
</>
)}
An Array.map() would always return an Array of same length, irrespective of you return anything or not. If you do not return anything then
it would be a sparse Array with empty values.
Try returning only the array with the required data. Here I have just separated out the Logos logic in a separate variable.
render() {
const Logos = (
<div className="homepage">
logos.reduce((acc, logo) => {
if (logo.active) {
const filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name));
Object.values(projects).forEach(({items}) => {
if (Object.values(items).includes(logo.name)) {
acc.push((<div>{val.title}</div>));
}
});
}
return acc
}, [])
</div>
)
return (
<div className="container">
<Language />
{Logos}
// rest of the code
)
}

Using map to iterate through two arrays

Currently in React, I am using array.map(function(text,index){}) to iterate through an array. But, how am I going to iterate through two arrays simultaneously using map?
EDIT
var sentenceList = sentences.map(function(text,index){
return <ListGroupItem key={index}>{text}</ListGroupItem>;
})
return (
<div>
<ListGroup>
{sentenceList}
</ListGrouup>
</div>
);
Like, in this I want icons to be prepended with every iteration. And I'm planning to have those icons in another array. So, thats why iterate two arrays.
If at all possible, I would recommend storing the text alongside the images in an array of objects, eg:
const objects = [{text: 'abc', image: '/img.png' }, /* others */];
that way you can just iterate through the array and select both members at the same time, for example:
objects.map(item => (<Component icon={item.image} text={item.text} />) )
If this isn't possible then just map over one array and access the second array's members via the current index:
sentences.map((text, index) => {
const image = images[index];
return (<Component icon={image} text={text} />);
});
Are the both arrays of same length? You can do something like below if your intention is to combine both in some way.
array.map(function(text,index){
return text + ' ' + array2[index]
})
In your case:
var sentenceList = sentences.map(function(text,index){
return <ListGroupItem key={index}>{text} <img src={icons[index]} /i> </ListGroupItem>;
})
return (
<div>
<ListGroup>
{sentenceList}
</ListGrouup>
</div>
);
Notice, How Icon src is being assigned. The idea is that access icons array with the same index to get a corresponding icon.
You can't do this with built-in Array.prototype methods, but you can use something like this:
function map2(arr1, arr2, func) {
return arr1.map(
(el, i) => { return func(el, arr2[i]); }
);
}
(Of course, arr1 and arr2 are expected to have the same length)
Generally, what you're looking for is a zip function, such as the one that lodash provides. Much like a real zipper, it combines two things of the same length into one:
const zipped = _.zip(sentences, icons);
return (
<div>
<ListGroup>
{zipped.map(([sentence, icon], index) => (
<ListGroupItem key={index}><Icon icon={icon} /> {text}</ListGroupItem>;
))}
</ListGroup>
</div>
);
Note, this is doing more iterations than are technically needed. If performance is an issue, you may want a solution that's a bit smart (not really in scope for your question though).

Rendering React Components from Array of Objects

I have some data called stations which is an array containing objects.
stations : [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
]
I'd like to render a ui component for each array position. So far I can write
var stationsArr = []
for (var i = 0; i < this.data.stations.length; i++) {
stationsArr.push(
<div className="station">
{this.data}
</div>
)
}
And then render
render(){
return (
{stationsArr}
)
}
The problem is I'm getting all of the data printing out. I instead want to just show a key like {this.data.call} but that prints nothing.
How can I loop through this data and return a new UI element for each position of the array?
You can map the list of stations to ReactElements.
With React >= 16, it is possible to return multiple elements from the same component without needing an extra html element wrapper. Since 16.2, there is a new syntax <> to create fragments. If this does not work or is not supported by your IDE, you can use <React.Fragment> instead. Between 16.0 and 16.2, you can use a very simple polyfill for fragments.
Try the following
// Modern syntax >= React 16.2.0
const Test = ({stations}) => (
<>
{stations.map(station => (
<div key={station.call} className='station'>{station.call}</div>
))}
</>
);
// Modern syntax < React 16.2.0
// You need to wrap in an extra element like div here
const Test = ({stations}) => (
<div>
{stations.map(station => (
<div className="station" key={station.call}>{station.call}</div>
))}
</div>
);
// old syntax
var Test = React.createClass({
render: function() {
var stationComponents = this.props.stations.map(function(station) {
return <div className="station" key={station.call}>{station.call}</div>;
});
return <div>{stationComponents}</div>;
}
});
var stations = [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
];
ReactDOM.render(
<div>
<Test stations={stations} />
</div>,
document.getElementById('container')
);
Don't forget the key attribute!
https://jsfiddle.net/69z2wepo/14377/
I have an answer that might be a bit less confusing for newbies like myself. You can just use map within the components render method.
render () {
return (
<div>
{stations.map(station => <div key={station}> {station} </div>)}
</div>
);
}
this.data presumably contains all the data, so you would need to do something like this:
var stations = [];
var stationData = this.data.stations;
for (var i = 0; i < stationData.length; i++) {
stations.push(
<div key={stationData[i].call} className="station">
Call: {stationData[i].call}, Freq: {stationData[i].frequency}
</div>
)
}
render() {
return (
<div className="stations">{stations}</div>
)
}
Or you can use map and arrow functions if you're using ES6:
const stations = this.data.stations.map(station =>
<div key={station.call} className="station">
Call: {station.call}, Freq: {station.frequency}
</div>
);
This is quite likely the simplest way to achieve what you are looking for.
In order to use this map function in this instance, we will have to pass a currentValue (always-required) parameter, as well an index (optional) parameter.
In the below example, station is our currentValue, and x is our index.
station represents the current value of the object within the array as it is iterated over.
x automatically increments; increasing by one each time a new object is mapped.
render () {
return (
<div>
{stations.map((station, x) => (
<div key={x}> {station} </div>
))}
</div>
);
}
What Thomas Valadez had answered, while it had provided the best/simplest method to render a component from an array of objects, it had failed to properly address the way in which you would assign a key during this process.
There are couple of way which can be used.
const stations = [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
];
const callList = stations.map(({call}) => call)
Solution 1
<p>{callList.join(', ')}</p>
Solution 2
<ol>
{ callList && callList.map(item => <li>{item}</li>) }
</ol>
Of course there are other ways also available.

Categories

Resources