How to iterate over array nested in object as props - javascript

I have a JSON file that I'm parsing for data but I am trying to map a subarray (nested in an object). However, I am getting an error sayin that the array is not iterable. I logged the array to the console where it prints the array but when I check its type it says "object".
Here is my code:
export default function Projects({ children, ...props }) {
return (
<div>
<div>
<div className={styles.text}>
<p>{props.description}</p>
<ul>
{props.features.map((feature) => (
<li>{feature}</li>
))}
</ul>
</div>
</div>
</div>
);
}
The JSON file:
[
{
"id": 1,
"name": "Netflix Clone",
"img": "/netflix-clone.jpg",
"direction": "row",
"description": "This project is a minimalistic Netflix clone utilising Firefox for storage and authorisation. It utilises Styled Components for styling, compound components, large-scale React architecture, and custom hooks.",
"features": [
"React",
"Styled Components",
"Compound components",
"Large-Scale React Architecture",
"Firebase (Firestore & Auth)",
"Functional components",
"Firebase (Firestore & Auth)",
"Custom hooks"
]
},
]
The error:
TypeError: Cannot read property 'map' of undefined

When loading data async the initial renders of your components will not have access to the data (the data will be undefined).
Your components should be coded to cope with this situation by displaying a different view, such as loading animations.
This could be achieved by simply checking if props.features is defined before rendering out your components:
export default function Projects({ children, ...props }) {
return (
<div>
<div>
<div className={styles.text}>
<p>{props.description}</p>
<ul>
{
/** Conditionally show only when props.features has a truthy value **/
!!props.features && props.features.map((feature) => (
<li>{feature}</li>
))
}
</ul>
</div>
</div>
</div>
);
}
To show another component / text while the data is loading you could use a ternary statement:
export default function Projects({ children, ...props }) {
return (
<div>
<div>
<div className={styles.text}>
<p>{props.description}</p>
<ul>
{
/** Ternary statement to show components when props.features is a truthy value
or loading when a falsy value **/
props.features ?
props.features.map((feature) => (
<li>{feature}</li>
)) :
"Loading..."
}
</ul>
</div>
</div>
</div>
);
}

On initial render, there is no data yet in features. Use condition like this ->
props && props.features && props.features.map((feature) => (
<li>{feature}</li>
))}

Related

Iterate nested object to access the key and values in react js

I have a nested object which is getting from api response, need to iterate that nested object based keys and values and the structure like this,
Have tried but not getting the expected output.
Code: Api response
{
"dashboard": "Dashboard",
"users": "Users",
"page_builder": "Page Builder",
"filemanager": {
"brand": "Brand Images",
"manufacturer": "Manufacturer Images"
},
"catalog": {
"catalog_product": "Product"
},
"coupon": "Coupon",
"egift": "E-gifting",
"paymentconfig": {
"configuration": "Gateway Config",
},
"app": {
"app_general": "General Config",
"forceupdate_config": "Force Update Config",
},
"apppayment": "Kapture Category",
"kapturecrm": "Vertical Master",
"phpinfo": "PHP Info"
}
When i tried from my end, am getting the output like this,
Tried sample code:
{Object.keys(roletest).map((key, idx) => (
<CFormCheck
id="validationrole_access"
key={idx}
name="role_access"
label={roletest[key]}
value={roletest[key]}
onChange={handleChange}
aria-describedby="inputGroupPrepend"
/>
))}
My Expected output:
Dashboard
Users
Page Builder
filemanager
Brand Images
Manufacturer Images
catalog
Product
Coupon
E-gifting
paymentconfig
Gateway Config
app
General Config
Force Update Config
Kapture Category
Vertical Master
PHP Info
My output:
Dashboard
Users
Page Builder
Coupon
E-gifting
Kapture Category
Vertical Master
PHP Info
Please do my needs
It sounds like you want to create a nested list. Which can actually be done quite easily. You were on the right track using an object method to iterate over the properties but Object.entries might be a little easier.
So, the trick is to make sure you use separate components for the list, and the list items (List/ListItem). In List, as you iterate over the items check if an item is an object. If it is create a new list with the List component, otherwise return a list item.
const data={dashboard:"Dashboard",users:"Users",page_builder:"Page Builder",filemanager:{brand:"Brand Images",manufacturer:"Manufacturer Images"},catalog:{catalog_product:"Product"},coupon:"Coupon",egift:"E-gifting",paymentconfig:{configuration:"Gateway Config"},app:{app_general:"General Config",forceupdate_config:"Force Update Config"},apppayment:"Kapture Category",kapturecrm:"Vertical Master",phpinfo:"PHP Info"};
function Example({ data }) {
return <List list={data} />;
}
function List({ list }) {
return (
<ul>
{Object.entries(list).map((item, key) => {
return <ListItem key={key} item={item} />;
})}
</ul>
);
}
function ListItem({ item }) {
const [label, value] = item;
if (typeof value === 'object') {
return (
<li>
{label}
<List list={value} />
</li>
);
}
return <li>{value}</li>;
}
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to access nested JSON graphql object passed into react child component, then list those items?

GraphQL:
{
"data": [
"theProducts": {
"id": "1",
"name": "Fitness bands",
"resistanceLevels": {
"UltraHeavy": 10,
"Heavy": 8,
"Medium": 6 },
"prices": [
16.8,
24.9
13.2
]
}
]
}
I am trying to get the resistanceBands JSON object and the price array to map to the react child component (the query is defined in the parent component) and render the items in a list with bullet points.
Parent Component:
const GET_PRODUCT_DATA = gql`
query getProducts {
theProducts {
id
name
resistanceLevels
prices
}
}
`
// How I am mapping data (name, etc) into the child component
const productsToRender = data.theProducts
{productsToRender.map( product => <ProductDisplay key={product.id} product={ product } />) }
// How can map the object and array to display their items to the ProductDisplay child component?
Child Component:
<div>
<h1>{product.name}</h1> // This works
<p>Resistance Levels | Intensity:</p>
<ul>
<li>{product.resistanceLevels}</li> // This doesnt
</ul>
<p>Prices</p>
<ul>
<li>{product.prices}</li> // This doesnt
</ul>
</div>
You need to use .map() for prices also because that's an array as:
<ul>
{product.prices.map(p => <li>{p}</li>)}
</ul>
Also for resistanceLevels you can use Object.keys and .map() combination as:
const resistanceLevels = {
"UltraHeavy": 10,
"Heavy": 8,
"Medium": 6
};
const result = Object.keys(resistanceLevels)
.map(k => resistanceLevels[k]);
console.log(result);
Read from the documentation:
The Object.keys() method returns an array of a given object's own enumerable property names, iterated in the same order that a normal loop would.
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
I guess this gives you the idea how to move further based on the example of prices.map().
const ParentComponent =()=>{
return(
<div>
{productsToRender.map(product => <ProductDisplay key={product.id} product={product }/>) }
</div>
)
}
export default ParentComponent;
const ProductDisplay =(props)=>{
return (
<div>
<h1>{product.name}</h1>
<p>Resistance Levels | Intensity:</p>
<ul>
{Object.entries(props.product.resistanceLevels).map(([key, value]) =>{
return(
<li>{key} : {value}</li>
)
})}
</ul>
<ul>
{
props.product.prices.map(item => {
<li>{item}</li>
})
}
</ul>
</div>
)
}

React - how to set inline CSS for each element of map function?

I have an application in React where I'm trying to read in data from a JSON file. The JSON file is in the following format:
[
{
"name": "Max",
"age": "21",
},
{
"name": "Sam",
"age": "18",
}
........
]
I have successfully read in the right data and displayed it on my screen, like this:
function foo(){
const styling = css`
font-size: 30px;
`;
return(
<div>
{people.map((person, i) => <Format key={i} {...person} css={styling}/>)}
</div>
);
}
Although all the information correctly displays on the screen, the styling is not getting applied to each person. How could I change this?
EDIT
Format component:
function Format({name, age}){
return (
<div>
<h1>{name}</h1>
<h2>{age}</h2>
</div>
);
}
function Format({name, age, css}){
return (
<div css={css}>
<h1>{name}</h1>
<h2>{age}</h2>
</div>
);
}
you passed styled to your component but you didnt use them in your child component
and plus using idx as key is not the best practice. following article explains why.
https://reactjs.org/docs/lists-and-keys.html
if name is unique you can pass each items name to mapped children.

Each child in an array or iterator should have a unique "key" prop in reactjs

I totally confused, because I have mistake in my console and I read reactjs documentation and all tips on stackoverflow, but I can't unterstand what problem is.
I see list of book's titles ({item.volumeInfo.title}), but console has error.
Here is my code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class BookList extends Component {
renderBook(mainData) {
return(
<ul>
{mainData.items.map((item, i) => {
return <li key={i} item={item}>{item.volumeInfo.title}</li>
})}
</ul>
)
}
render(){
return (
<div className="book-row">
<div className="book-info">
{this.props.book.map(this.renderBook)}
</div>
</div>
);
}
}
function mapStateToProps({book}) {
return {book};
}
export default connect(mapStateToProps)(BookList);
It is part of API response:
{ "kind": "books#volumes",
"totalItems": 288,
"items": [
{
"kind": "books#volume",
"id": "yXlOAQAAQBAJ",
"etag": "CG7f2mQ+7Nk",
"selfLink": "https://www.googleapis.com/books/v1/volumes/yXlOAQAAQBAJ",
"volumeInfo": {
"title": "Nineteenth Century Home Architecture of Iowa City",
"subtitle": "A Silver Anniversary Edition"
I tried to do the next keys:
key={item.etag}, key={i}, key={item.volumeInfo.title}
but error is still here.
Please help.
Thank you so much.
Since you are mapping over book:
{this.props.book.map(this.renderBook)}
the ul also needs a key prop:
renderBook(mainData, bookIdx) {
return(
<ul key={bookIdx}>
{mainData.items.map((item, i) => {
return <li key={i} item={item}>{item.volumeInfo.title}</li>
})}
</ul>
)
}
This is because there will be many ul siblings and React needs to tell the difference (same as with li).
However, it is better (if possible) to use a key that is not the index of the array. So, if book and item have a unique identifier, it would be best to use that.
So, it looks like you have another array outside of the sample data you provided:
[
{ "kind": "books#volumes",
"totalItems": 288,
"items": [
{
Reason is you are using two map, and assigning the key in only one, assign the key to <ul> also, because of that it is giving you the error, Try this:
renderBook(mainData, index) {
return(
<ul key={index}>
{mainData.items.map((item, i) => {
return <li key={i} item={item}>{item.volumeInfo.title}</li>
})}
</ul>
)
}

Rendering an array.map() in React

I am having a problem where I am trying to use array of data to render a <ul> element. In the code below the console logs are working fine, but the list items aren't appearing.
var Main = React.createClass({
getInitialState: function(){
return {
data: dataRecent
}
},
render: function(){
return (
<div>
<ul>
{
this.state.data.map(function(item, i){
console.log('test');
<li>Test</li>
})
}
</ul>
</div>
)
}
});
ReactDOM.render(<Main />, document.getElementById('app'));
What am I doing wrong? Please feel free to point out anything that isn't best practice.
Gosha Arinich is right, you should return your <li> element.
But, nevertheless, you should get nasty red warning in the browser console in this case
Each child in an array or iterator should have a unique "key" prop.
so, you need to add "key" to your list:
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
or drop the console.log() and do a beautiful oneliner, using es6 arrow functions:
this.state.data.map((item,i) => <li key={i}>Test</li>)
IMPORTANT UPDATE:
The answer above is solving the current problem, but as Sergey mentioned in the comments: using the key depending on the map index is BAD if you want to do some filtering and sorting. In that case use the item.id if id already there, or just generate unique ids for it.
You are not returning. Change to
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>;
})
let durationBody = duration.map((item, i) => {
return (
<option key={i} value={item}>
{item}
</option>
);
});
Using Stateless Functional Component We will not be using this.state. Like this
{data1.map((item,key)=>
{ return
<tr key={key}>
<td>{item.heading}</td>
<td>{item.date}</td>
<td>{item.status}</td>
</tr>
})}
You are implicitly returning undefined. You need to return the element.
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>
})
Best Answer:
import React, { useState } from 'react';
import './App.css';
function App() {
// Array of objects containing our fruit data
let fruits = [
{ label: "Apple", value: "🍎" },
{ label: "Banana", value: "🍌" },
{ label: "Orange", value: "🍊" }
]
// Using state to keep track of what the selected fruit is
let [fruit, setFruit] = useState("⬇️ Select a fruit ⬇️")
// Using this function to update the state of fruit
// whenever a new option is selected from the dropdown
let handleFruitChange = (e) => {
setFruit(e.target.value)
}
return (
<div className="App">
{/* Displaying the value of fruit */}
{fruit}
<br />
<select onChange={handleFruitChange}>
{
fruits.map((fruit) => <option value={fruit.value}>{fruit.label}</option>)
}
</select>
</div>
);
}
export default App;
Add up to Dmitry's answer, if you don't want to handle unique key IDs manually, you can use React.Children.toArray as proposed in the React documentation
React.Children.toArray
Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.
Note:
React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it.
<div>
<ul>
{
React.Children.toArray(
this.state.data.map((item, i) => <li>Test</li>)
)
}
</ul>
</div>
I've come cross an issue with the implementation of this solution.
If you have a custom component you want to iterate through and you want to share the state it will not be available as the .map() scope does not recognize the general state() scope.
I've come to this solution:
`
class RootComponent extends Component() {
constructor(props) {
....
this.customFunction.bind(this);
this.state = {thisWorks: false}
this.that = this;
}
componentDidMount() {
....
}
render() {
let array = this.thatstate.map(() => {
<CustomComponent that={this.that} customFunction={this.customFunction}/>
});
}
customFunction() {
this.that.setState({thisWorks: true})
}
}
class CustomComponent extend Component {
render() {
return <Button onClick={() => {this.props.customFunction()}}
}
}
In constructor bind without this.that
Every use of any function/method inside the root component should be used with this.that
Dmitry Brin's answer worked for me, with one caveat. In my case, I needed to run a function between the list tags, which requires nested JSX braces. Example JSX below, which worked for me:
{this.props.data().map(function (object, i) { return <li>{JSON.stringify(object)}</li> })}
If you don't use nested JSX braces, for example:
{this.props.data().map(function (object, i) { return <li>JSON.stringify(object)</li>})}
then you get a list of "JSON.stringify(object)" in your HTML output, which probably isn't what you want.
import React, { Component } from 'react';
class Result extends Component {
render() {
if(this.props.resultsfood.status=='found'){
var foodlist = this.props.resultsfood.items.map(name=>{
return (
<div className="row" key={name.id} >
<div className="list-group">
<a href="#" className="list-group-item list-group-item-action disabled">
<span className="badge badge-info"><h6> {name.item}</h6></span>
<span className="badge badge-danger"><h6> Rs.{name.price}/=</h6></span>
</a>
<a href="#" className="list-group-item list-group-item-action disabled">
<div className="alert alert-dismissible alert-secondary">
<strong>{name.description}</strong>
</div>
</a>
<div className="form-group">
<label className="col-form-label col-form-label-sm" htmlFor="inputSmall">Quantitiy</label>
<input className="form-control form-control-sm" placeholder="unit/kg" type="text" ref="qty"/>
<div> <button type="button" className="btn btn-success"
onClick={()=>{this.props.savelist(name.item,name.price);
this.props.pricelist(name.price);
this.props.quntylist(this.refs.qty.value);
}
}>ADD Cart</button>
</div>
<br/>
</div>
</div>
</div>
)
})
}
return (
<ul>
{foodlist}
</ul>
)
}
}
export default Result;

Categories

Resources