Rendering React Components from Array of Objects - javascript

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.

Related

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

How can I insert sequential values from an array into another mapped array?

I'm fairly new to Javascript and I'm having issues with a component I'm playing with.
I have an array of data that I am successfully mapping into a component. I have another array that contains the visual properties I wish to pass in as props. For example, I'd like the first returned to be red, the next blue, the last green and then repeat the sequence. How might I do this?
I've had a crack at it but you will immediately see why my solution hasn't worked. I understand why this hasn't worked but I'm not sure what to try next.
Can anyone point me in the right direction?
const colorsList = ["red", 'blue', "green"]
console.log(colorsList)
const IndustriesPage = ({data}) => (
<Layout>
<HeroInternal
title="Industries"
/>
<GridBlock>
{data.allContentfulPageCaseStudy.edges.map(function(target){
return(
<TextLinkModule
linkTitle = {target.node.industry}
titleModifier = 'textLinkModule__title--small'
backgroundColor = {colorsList.map(function(target){
return(
target,
console.log(target)
)
}
)}
linkDestination = {`industries/${target.node.industry}`.split(' ').join('-').split('&').join('and').toLowerCase()}
// backgroundImage = {target.node.linkBackgroundImage.fluid.src}
/>
)
})
}
</GridBlock>
<ContactUsBlock></ContactUsBlock>
</Layout>
)
The map function returns the value and the index of each member of the array. You can use the index of each edge to return a single value from the colorsList array.
data.allContentfulPageCaseStudy.edges.map(function(target, idx){
...
backgroundColor = { colorsList[idx % colorsList.length] }

React ternary operator used to iterate through a results array (otherwise undefined), is there a different, preferred approach?

I'm new to React and my current code works, but is there a better, "React way" (w/o ternary) of mapping through an empty array, before it has the results (without returning undefined)?
I'm rendering a materialize card for every result returned from an API search. What the code does isn't the issue because it works w/ my current solution.
Thanks in advance.
class NutritonixResultsDisplay extends Component {
render() {
const hits = this.props.nutritionixResults.hits;
return (
<div className='nutritionixResultsDiv'>
{hits
? hits.map((result, i) => (
<div key={i} className='nutritionixResultDiv'>
<Card className='cardDisplay' header={<CardTitle reveal image='' waves='light'/>}
title={result.fields.item_name}reveal={<div><p>{`Calories: ${result.fields.nf_calories}`}</p><p>{`Protein: ${result.fields.nf_protein}`}</p></div>}> <p>This is a link</p>
<p>{`Brand Name: ${result.fields.brand_name}`}</p>
</Card>
</div>
))
: ''}
</div>
);
}
}
export default NutritonixResultsDisplay;
As an alternative you can use || []:
(hits || []).map( // ... etc. )
Just simply make hits an empty array.
const hits = this.props.nutritionixResults.hits || [];

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

Using Filter Function to Return React Component - Code Review

I dont understand what I am doing wrong here. I am using Immutable.js & React in my app. I am calling Immutable.js's filter function to refine the collection based on the if condition & return array of React components.
Its actually return 'svgTemplateState' instead of the React component collection.
let getUpdatedTem = ( renderType, svgTemplateState ) => {
switch( renderType ){
case( "Template Selection" ):
return( svgTemplateState.filter(( templateType ) => {
if( templateType.get( "templateNo" ) > -1 ){
let temType = templateType.get( "type" );
return(
<TemplatePath
temData = { templateType }
key = { temType } />
);
}
}));
case( "Preview" ):
...
Immutable filters should return a boolean, indicating whether or not you want the template to be a part of the collection that you are returning. In your case, you're returning a React component.
You're iterating svgTemplateState, which appears to be a Map (it's not entirely clear to me what it is). What you should be iterating is a collection of templates, and check the template number on each template within the collection. You'd have something (simplified) like:
let newCollection = templatesCollection.filter( template => template.get( "id" ) > -1);
Is what you want to find the first Item that match the condition? like .findEntry() ?
return( svgTemplateState.filter(( templateType ) => {
if( templateType.get( "templateNo" ) > -1 ){
let temType = templateType.get( "type" );
return(
<TemplatePath
temData = { templateType }
key = { temType } />
);
}
}));
Assuming svgTemplateState is an array, replace the "filter" method in the above code with "map". If svgTemplateState is an object with pairs then use a for...in to iterate over svgTemplateState, check the condition and push the component to a temp object which you can return after the loop.

Categories

Resources