Render data from local JSON (with dynamic data) in ReactJs - javascript

I have a json array of objects as shown below, the number of objects can vary as well as the content of the "units" array inside the respective object can vary.
[{
"title": "title1",
"units": [{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
},
{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
},
{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
},
{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
}
]
},
{
"title": "title2",
"units": [{
"title": "unittitle1",
"status": "on"
},
{
"title": "unittitle1",
"status": "on"
}
]
}
]
I am calling the above json like this :
import React, { Component } from 'react';
import hardwareDetailsData from '../../../assets/json/UnitHardwareDetails.json';
import meter from '../../../assets/images/meter.svg';
class HardwareUnits extends Component {
constructor() {
super();
this.state = {
hardwareDetailsData: []
};
}
render() {
return hardwareDetailsData.map(hardwareData => (
<div className="f-14">
<ul className="list-inline list-pipe">
<li className="list-inline-item">
<span className="f-b">{hardwareData.units.title}</span>
<span className="status-text ml-2 text-success">{hardwareData.units.status}</span>
</li>
<li className="list-inline-item">
<span className="text-warning f-b">{hardwareData.units.utilization}</span>
<img src={meter} alt="Meter" className="ml-3 icon-meter-size" />
</li>
</ul>
</div>
));
}
}
export default HardwareUnits;
I am aware that the above code would require unique key for child elements. But, is there a optimal way to achieve the main objective i.e. to render the dynamic content of JSON in the list

I understood you want map in map like so:
return hardwareDetailsData.map(({ title, units }) => (
<div className="title">
{title}
units.map({ title, status, utilization}) => (
<div className="status">{status}</div>
));
</div>
));
Keep in mind shadowing, key (as you mentioned) and conditional rendering for utilization if it's the case. Also it could be splitted to components.

Your render method should be like this:
render() {
return hardwareDetailsData.map((hardwareItem, index) => (
<div className="f-14" key={index}>
<ul className="list-inline list-pipe">
{
hardwareItem.units.map((unitItem, index) => (
<li className="list-inline-item" key={index}>
<span className="f-b">{unitItem.title}</span>
<span className="status-text ml-2 text success">{unitItem.status}</span>
<span className="text-warning f-b">{unitItem.utilization}</span>
<img src={meter} alt="Meter" className="ml-3 icon-meter-size" />
</li>
))
}
</ul>
</div>
));
}

Related

Avoid unnecessary component rendering with memo in nextjs

I'am trying to understand react's behaviour throught nextjs.
I have an index.js page with one component Homecard displayed three times and one button that increment a value.
Each time I click on button all Homecard components are re-render.
index.js
import { Homecard } from '../components/Homecard'
import { useState } from 'react'
export default function Home() {
const [count, increment] = useState(0);
const homecards = [
{
"main": "main0",
"sub": "sub0",
"desc": "Desc0",
"nav": [{
"href": "/page0",
"value": "see"
}]
},
{
"main": "main1",
"sub": "sub1",
"desc": "Desc1",
"nav": [{
"href": "/page1",
"value": "see"
}]
},
{
"main": "main2",
"sub": "sub2",
"desc": "Desc2",
"nav": [{
"href": "/page2",
"value": "see"
}]
}
];
const handleCount = () => {
increment(count => count + 1);
}
return (
<>
<div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
{
homecards.map((homecard, index) => (
<Homecard
key={index}
main={homecard.main}
sub={homecard.sub}
desc={homecard.desc}
nav={homecard.nav}
/>
))
}
</div>
<button onClick={handleCount}>increment {count}</button>
</>
)
}
homecard.js
export default function Homecard({ main, sub, desc, nav }) {
console.log('render Homecard');
return (
<div className={`${styles.homecard}`}>
<div>
<h3>
{main}
{sub &&
<span>{sub}</span>
}
</h3>
<p>{desc}</p>
{nav &&
<ul>
{nav.map((link, index) => (
<li key={index}>
<Link href={link.href}>
<a>{link.value}</a>
</Link>
</li>
))}
</ul>
}
</div>
</div>
)
}
I tried to wrap my Homecard with React.memo like so
const Homecard = React.memo(({ main, sub, desc, nav }) => {})
But I still see console.log('render Homecard'); when my button is clicked.
How can I could update only my button and not others components ?
The problem is that you're recreating your homecards array on every render of Home, so each nav object is a new object, and so React.memo sees a difference in the props and doesn't optimize away the subsequent re-renders.
There are a couple of ways to fix it:
Define homecards outside Home; it's unchanging, so there's no reason to recreate it every time Home is called.
If you couldn't do that for some reason, you could pass a second argument to React.memo, the "areEqual" function, which will receive the old props and the new ones; in the function, do a deep comparison to see if if nav objects have the same property values are the previous ones even though they're different objects.
Example of #1:
const { useState } = React;
/*export default*/ const Homecard = React.memo(({ img, main, sub, desc, nav }) => {
console.log('render Homecard');
return (
<div className={`${""/*styles.homecard*/}`}>
<div>
<h3>
{main}
{sub &&
<span>{sub}</span>
}
</h3>
<p>{desc}</p>
{nav &&
<ul>
{nav.map((link, index) => (
<li key={index}>
<a href={link.href}>
{link.value}
</a>
</li>
))}
</ul>
}
</div>
</div>
)
});
const homecards = [
{
"main": "main0",
"sub": "sub0",
"desc": "Desc0",
"nav": [{
"href": "/page0",
"value": "see"
}]
},
{
"main": "main1",
"sub": "sub1",
"desc": "Desc1",
"nav": [{
"href": "/page1",
"value": "see"
}]
},
{
"main": "main2",
"sub": "sub2",
"desc": "Desc2",
"nav": [{
"href": "/page2",
"value": "see"
}]
}
];
/*export default*/ function Home() {
const [count, increment] = useState(0);
const handleCount = () => {
increment(count => count + 1);
}
return (
// Sadly, Stack Snippets don't support <>...</>
<React.Fragment>
<div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
{
homecards.map((homecard, index) => (
<Homecard
key={index}
main={homecard.main}
sub={homecard.sub}
desc={homecard.desc}
nav={homecard.nav}
/>
))
}
</div>
<button onClick={handleCount}>increment {count}</button>
</React.Fragment>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Home />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
(Warning: I changed a couple of minor things to make this work in Stack Snippets, like replacing Link and using React.Fragment instead of <>...</>, so don't copy and paste it directly back into your project, just move homecards in your existing code.)

React JS - data.map can't create list with the data

I got data.js file with some basic info for food like:
export default [
{
"name": "Crock Pot Roast",
"information":[
{
"date":"24 July 2019",
"type": "Main dish",
"difficulty": "Easy",
"time": "~50",
}
],
"ingredients": [
{
"quantity": "1",
"name": " beef roast",
"type": "Meat"
}
],
...
Some more data
...
}
]
I want to create list on react that can get all elements from data.information which function will be correct to use?
I'm getting the data like this:
const getData = data.map(food => {
return (
<div key={food.name}>
<span>{food.name}</span>
<div>
<div>
<img src={food.imageURL} alt={food.name} />
<div>
<ul>{getFoodInformation}</ul>
</div>
</div>
</div>
<div>food ingredients</div>
<div>food recipe</div>
</div>
);
});
but I can't use food.information.map to create list like:
<ul>{food.information.map((info) => <div key={info.date}>{info}</div>) }</ul>
Any ideas maybe to use another function not map?
You're right that this line is the problem:
<ul>{food.information.map((info) => <div key={info.date}>{info}</div>) }</ul>
But, .map() is not a problem. Your information data is an array, so you can .map() it with JSX.
Your problem is this:
<div key={info.date}>{info}</div>
info is an object, and React doesn't know how to print an object "beautifully". You want to use the string data (for example, {info.type}).
If you need to dump the full object, use JSON.stringify like this:
<div key={info.date}>{JSON.stringify(info)}</div>
React does not render array/object into a list by default. you have to be explicit in your code.
your first attempt is 'close' to correct, however, you can notice, ingredients and information are both arrays of objects. it means that you have to generate a tag for each index.
I used JSON.stringify to generate a unique key since using an index for key is bad practice.
The code below shows you one of the correct approaches using react.
const data = [
{
name: "Crock Pot Roast",
imageURL: "https://via.placeholder.com/150",
information: [
{
date: "24 July 2019",
type: "Main dish",
difficulty: "Easy",
time: "~50"
}
],
ingredients: [
{
quantity: "1",
name: " beef roast",
type: "Meat"
}
]
}
];
const CardRecipe = ({data}) => {
const { name, imageURL, information, ingredients } = data;
return (
<div key={name}>
<span>{name}</span>
<div>
<div>
<img src={imageURL} alt={name} />
<div>
{information &&
information.map(info => {
const { date, type, difficulty, time } = info;
return (
<ul key={JSON.stringify(info)}>
<li> {date} </li>
<li> {type} </li>
<li> {difficulty} </li>
<li> {time} </li>
</ul>
);
})}
</div>
<div>
{ingredients &&
ingredients.map(ingredient => {
const { quantity, name, type } = ingredient;
return (
<ul key={JSON.stringify(ingredient)}>
<li> {quantity} </li>
<li> {name} </li>
<li> {type} </li>
</ul>
);
})}
</div>
</div>
</div>
<div>food ingredients</div>
<div>food recipe</div>
</div>
);
};
export default function App() {
const first_reciepe = data[0];
return (
<div className="App">
<CardRecipe data={first_reciepe} />
</div>
);
}
Alternatively, you can print any lists by mapping once on the array, and then on each object/key to generate (x) amount of <"li"> tags.
Example:
const DoList = ({ data }) => {
return data.map(list => {
return (
<ul key={list}>
{Object.keys(list).map(key => {
const value = list[key];
return <li key={key}> {value} </li>;
})}
</ul>
);
});
};
Then you would use it as many time as you want like this:
<DoList data={ingredients} />
<DoList data={information} />
<DoList data={...} />

How can I loop over a JSON object in React

I'm trying to retrieve information from the following JSON object data.json:
{
"status": "ok",
"feed": {
"title": "NOS Nieuws",
},
"items": [
{
"title": "Test Title",
"description": "Test description",
"enclosure": {
"link": "https://examplelink.com/1008x567.jpg",
"type": "image/jpeg"
},
"categories": []
},
{
"title": "Test Title 2",
"description": "Test 2",
"enclosure": {
"link": "link": "https://examplelink.com/1008x567.jpg",
"type": "image/jpeg"
},
"categories": []
}
]
}
So I want to loop over this JSON object to display all the available items and its corresponding title, description and enclosure-link.
I know i can access them separately as:
const items = data.items;
const title = items.title;
const url = items.enclosure.link;
Usually I would do a for-loop and loop through data.items[i]. However, since this is a react and an object instead of an array it works differently.
My current code:
class Feed extends Component {
render() {
const items = data.items[0];
const title = items.title;
const url = items.enclosure.link;
const description = items.description;
const feed = [
{
url: url,
title: title,
description: description
}
];
return (
<div className="feed">
<h1>Newsfeed</h1>
<div className="columns is-multiline">
{feed.map(article => (
<div className="column is-one-third">
<NewsCard
article={article}
title={items.title}
description={items.description}
/>
</div>
))}
</div>
</div>
);
}
}
Right now its only displaying the first entry of the object (because it has const items = data.items[0]) How can I loop over data.json and display its content in the NewsCard component? I know that each child should have a unique 'key' prop but thats where I'm stuck.
I want to loop over this JSON object to display all the available
items and its corresponding title, description and enclosure-link
Then instead of doing this:
const items = data.items[0];
Try this:
const items = data.items;
Then, you can use the map function, like this:
items.map(item => (
<div className="column is-one-third">
<NewsCard
article={item.enclosure.link}
title={item.title}
description={item.description}
/>
</div>
));
You could do something like this.
class Feed extends Component {
render() {
let newsCards = data.items.map(item => {
<div className="column is-one-third">
<NewsCard
article={item}
title={item.title}
description={item.description}
/>
</div>
});
return (
<div className="feed">
<h1>Newsfeed</h1>
<div className="columns is-multiline">
{newsCards}
</div>
</div>
);
}
}
const feed = data.items
{feed.map(item => (
<div className="column is-one-third">
<NewsCard
article={item.enclosure.link}
title={item.title}
description={item.description}
/>
</div>
))}
try this way

Filtering array and then map array in array

Pretty sure that I am missing something simple but this code has me banging my head.
I have an array in a local json file as
{
"type": [
{
"name": "Accommodation",
"icon": "🏨",
"subcategory": [
"Hotels",
"Motels",
"Bed & Breakfasts"
]
},
{
"name": "Guided Tours",
"icon": "🚌",
"subcategory": [
"Audio & Self Guided",
"Cruises, Sailing & Water",
"Food, Wine & Nightlife"
]
}
]
}
and importing into a React component as
import { type } from './assets/json/company-type.json';
then I am using filter to the name of companyType (which is based on a select value of either Accommodation or Guided Tours. After I map the returned items before map the subcategory
<ul>
{type
.filter(item => {
return item.name === companyType;
})
.map(item => {
item.subcategory.map(subcategory => {
<li key={subcategory}>
<label id={subcategory} htmlFor={subcategory}>
<input
type="radio"
name={subcategory}
value={subcategory}
/>
<span />
{subcategory}
</label>
</li>;
});
})}
</ul>
However, nothing is returned. Yet if I log subcategory I get each returned in the console. What am I missing?
This is because you are not returning from .map() methods.
Use this modified code and it will work as expected
<ul>
{type
.filter(item => {
return item.name === "Accommodation";
})
.map(item => {
return item.subcategory.map(subcategory => {
return (
<li key={subcategory}>
<label id={subcategory} htmlFor={subcategory}>
<input
type="radio"
name={subcategory}
value={subcategory}
/>
<span />
{subcategory}
</label>
</li>
);
});
})}
</ul>
codeSandbox link: https://codesandbox.io/s/4881483j7x

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

Categories

Resources