Rendering a list of nested objects in Reacts - reactjs

I have a list of objects:
const products2 = [
{category: "Category 1", products:
{
product1: "1",
product2: "2"
}
},
{category: "Category 2", products:
{
product1: "3",
product2: "4"
}
},
]
How can I render it in a div?
Tried to map it but it didn't work :(
Thanks,
Kuba

products is an array so map over it should be fine. products on the other hand is not and therefore map won't work. You can use, for example, Object.values(products2[0].products) so you get 1 and 2 in an array and do what you need with it.
const products2 = [
{
category: "Category 1",
products: {
product1: "1",
product2: "2"
},
},
{
category: "Category 2",
products: {
product1: "3",
product2: "4"
},
},
]
const result = products2
.map(product => `${product.category} ${Object.values(product.products).join(' ')}`)
console.log(result)
Same applies in a react component:
render() {
return (
<div>
{products2
.map(product =>
<div>
{product.category}
{Object.values(product.products).map(name => <p>{name}</p>)}
</div>
)
}
</div>
)
}
PS: You don't need to map twice as I did. Mapping only once and returning the div right away is also fine.

Related

Error when changing the value of an object within an array causing another object to be called

I'm working on a project where I need the values in an array to change depending on if a button is clicked for that specific item. Right now, I'm able to get the first name (pescatarian) in my first subCategories object to work, but when I press the second one (vegan), it is displaying my second subCategories object and changing data[0] to
"category": "Diet", "subCategories": [{"name": "one", "value": false}, {"name":"two", "value": false}, {"name": "three", "value": false}, {"name": "four", "value": false}]}
from:
{"category": "Diet", "subCategories": [{"name": "pescatarian", "value": false}, {"name": "vegan", "value": false}, {"name": "vegetarian", "value": false}]}
From there, I am able to press the first two subCategories (one and two), but if I press the third one I get an error stating: TypeError: undefined is not an object (evaluating 'data[index].subCategories'). Does anyone know why this is happening? I would really appreciate any help or advice. Thank you!
const [data, setData] = useState([
{
category: 'Diet',
subCategories: [
{name:'pescatarian', value: false },
{name:'vegan', value: false },
{name:'vegetarian', value: false }
],
}, {
category: 'Daily Exercise in Hours',
subCategories: [
{name:'one', value: false },
{name:'two', value: false },
{name:'three', value: false },
{name:'four', value: false }
],
},
])
const onChangeValue = (item, index, newValue) => {
console.log('data[index]:', data[index].subCategories)
const newSub = data[index].subCategories.map(subCat => {
if (subCat.name.includes(item.name)) {
return {
...subCat,
value: newValue,
}
}
return subCat
})
const newData = data.map(newItem => {
if (newItem.subCategories.some(x => x.name === item.name)){
return {
...newItem,
subCategories:newSub,
}
}
return newItem
})
setData(newData)
}
onValueChange={newValue => onChangeValue(item, index, newValue)}

React: How to render a list of items grouped by category (using .map)

I have an array (myArray), stored like so (in firebase):
[
{
"id": "1",
"Category": "Hardware",
"Name": "Xtreme"
},
{
"id": "123",
"Category": "Software",
"Name": "Obsolete"
},
{
"id": "12345",
"Category": "Software",
"Name": "V1"
},
{
"id": "1234567",
"Category": "Hardware",
"Name": "CPU"
}
]
I am using the following code:
const sorterAR = [];
myArray.forEach((item) => {
let cat = sorterAR.find(
(cat) => cat.id === item.id
);
if (!cat) {
cat = {
id: item.id,
Category: item.Category,
items: [],
};
sorterAR.push(cat);
}
cat.items.push(item);
});
And then displaying like so:
<div className="App">
{sorterAR.map((cat) => (
<>
<div>
<b>{cat.Category}</b>
</div>
<ul>
{cat.items.map((item) => (
<li>{item.Name}</li>
))}
</ul>
</>
))}
</div>
This works in that it produces an output like:
**Hardware**
Xtreme
**Hardware**
CPU
**Software**
Obsolete
**Software**
V1
How do I alter this to produce the following output:
**Hardware**
Xtreme
CPU
**Software**
Obsolete
V1
So that it displays the category name and then all the items in that category, and then moves to the next one and so forth?
I assumed that order doesn't matter if Hardware or Software should come first.
First I categorized the array into an object of Category objects using Array.prototype.reduce().
From the resultant object you can build the JSX
var data1 = [
{
id: '1',
Category: 'Hardware',
Name: 'Xtreme',
},
{
id: '123',
Category: 'Software',
Name: 'Obsolete',
},
{
id: '12345',
Category: 'Software',
Name: 'V1',
},
{
id: '1234567',
Category: 'Hardware',
Name: 'CPU',
},
];
const categorizedData = data1.reduce((acc, curr) => {
const { id, Category, Name } = curr;
if (!acc[Category]) {
acc[Category] = {
items: [],
};
}
acc[Category].items.push(Name);
return acc;
}, {});
console.log(categorizedData);
Object.keys(categorizedData).map((key, index) => {
console.log(`Category: ${key}`);
categorizedData[key].items.map((item, index) =>
console.log(`Item ${index}: ${item}`)
);
});

React - how to pass props with different names to same component

I have a card component that receives a title, description, and image props, however the data that I receive from two different sources labels these props differently.
Source 1 (carouselContent):
[
{
"id": "1",
"title": "title 1",
"description": "Description text 1",
"image": {
"fluid": {
"src": "/get-job-you-want.jpg?w=800&q=50"
}
}
},
{
"id": "2",
"title": "title 2",
"description": "Description text 2",
"image": {
"fluid": {
"src": "/framing-a-high-deck-1.jpg?w=800&q=50"
}
}
}
]
This passed onto an <ImageSlider/> component like so:
<ImageSlider data={carouselContent} />
Then next source (relatedPrograms) looks like this:
[
{
"fullProgramName": "title 1",
"id": "1",
"metaDescription": "description 1",
"heroImage": {
"fluid": {
"src": "/denys-nevozhai-100695.jpg?w=350&h=196&q=50&fit=scale"
}
}
},
{
"fullProgramName": "title 2",
"id": "2",
"metaDescription": "description 2",
"heroImage": null
}
]
and to be called like so:
<ImageSlider data={relatedPrograms} />
How do I structure the component to be able to handle both the title and image coming from source 1 and the fullProgramName and heroImage coming from source 2?
Here's a quick suggestion on how to map your two data sources to have a common shape. Obviously you'd modify this to contain the properties that were important to your ImageSlider component that renders the images. Here I have just picked a couple fields from your example data. The important feature here is that no matter where the data come from (carousel versus related), you transform them to represent a set of images, where their origin doesn't matter and they are indistinguishable to the ImageSlider. ImageSlider probably just cares about relevant image data, so decide on a shape that represents your basic image data.
Also a codesandbox for this: https://codesandbox.io/s/dank-morning-obwld
const normalizedCarousel = carouselContent.map((item) => ({
id: item.id,
name: item.title,
src: item.image.fluid.src
}));
const normalizedRelated = relatedPrograms.map((item) => ({
id: item.id,
name: item.fullProgramName,
src: item.heroImage?.fluid.src
}));
const ImageSlider = ({ header, data }) => {
// This component now just renders a list with relevant props
// but in the real app would render the slider.
return (
<>
<h2>{header}</h2>
<ul>
{data.map((item) => {
const { id, name, src } = item;
return (
<li>
Id: {id}, Name: {name}, Src: {src}
</li>
);
})}
</ul>
</>
);
};
export default function App() {
return (
<>
<ImageSlider header="Carousel" data={normalizedCarousel} />
<ImageSlider header="Related" data={normalizedRelated} />
</>
);
}
Either you can preprocess or combine your data just before passing it as a prop to your component or you can use a secondary prop and populate only the values from your secondary data source.
const a =[
{
"id": "1",
"title": "title 1",
"description": "Description text 1",
"image": {
"fluid": {
"src": "/get-job-you-want.jpg?w=800&q=50"
}
}
},
{
"id": "2",
"title": "title 2",
"description": "Description text 2",
"image": {
"fluid": {
"src": "/framing-a-high-deck-1.jpg?w=800&q=50"
}
}
}
]
const b = [
{
"fullProgramName": "title 1",
"id": "1",
"metaDescription": "description 1",
"heroImage": {
"fluid": {
"src": "/denys-nevozhai-100695.jpg?w=350&h=196&q=50&fit=scale"
}
}
},
{
"fullProgramName": "title 2",
"id": "2",
"metaDescription": "description 2",
"heroImage": null
}
]
const c = []
a.forEach((val, idx) => {
c.push({
title: val.title,
image: val.image,
fullProgramName: b[idx].fullProgramName,
heroImage: b[idx].heroImage
});
})
Then you can easily pass that copy of data to your component

How to search and filter in array of objects on setState

I'm trying to create a search based on an array of objects with react which data is in this format:
const data = [
{"category 1" : [
{
"name": "Orange",
"desc": "juice, orange, Water"
},
{
"name": "Ananas",
"desc": "juice, ananas, water"
}
]
},
{"category 2" : [
{
"name": "Banana Split",
"desc": "Banana, ice cream, chocolat, topping",
"allergens": "nuts"
},
{
"name": "Mango Sticky Rice",
"desc": "Mango, rice, milk",
"allergens": ""
}
]
}
]
I stored this data inside useState declaration to be able to render accordingly on data chnage:
const [filteredBySearch, setFilteredBySearch] = useState(data)
I have an input where we can type anything and set inside useState declaration.
Goal:
If I type in my input:
"Jui"
Output should be:
console.log(filteredBySearch)
/* output:
[
{"category 1" : [
{
"name": "Orange",
"desc": "juice, orange, Water"
},
{
"name": "Ananas",
"desc": "juice, ananas, water"
}
]
},
{"category 2" : []
}
]*/
Exemple 2:
If I type in my input:
"Orange banana"
Output should be:
console.log(filteredBySearch)
/* output: [
{"category 1" : [
{
"name": "Orange",
"desc": "juice, orange, Water"
}
]
},
{"category 2" : [
{
"name": "Banana Split",
"desc": "Banana, ice cream, chocolat, topping",
"allergens": "nuts"
}
]
}
]*/
I've try creating a new object with map and filter and set it with setFilteredBySearch, but I can't get anything, even creating this new object.
This the full component:
import Card from '../components/Card'
import React, { useState } from 'react';
export default function IndexPage({ data, search }) {
//search is the result of input value set on a useState
//Filter categoriesFoods by search
const [FilteredBySearch, setFilteredBySearch] = useState(data)
return (
<div className="main-content">
<div className="card-container">
{
FilteredBySearch.map(function(el, i) {
return (
<div key={i}>
<h2 className="category" id={Object.keys(el)}>{Object.keys(el)}</h2>
{
el[Object.keys(el)].map (function(itm,index){
return <Card key={index} infoItem={itm}/>
})
}
</div>
)
})
}
</div>
<style jsx>{`...`}</style>
</div>
)}
Any idea for me ?
Thanks a lot for your guidance!
I think this is what you are looking for. I have created below utilities for filtering as per your requirement.
const dataObj = [
{
'category 1': [
{
name: 'Orange',
desc: 'juice, orange, Water',
},
{
name: 'Ananas',
desc: 'juice, ananas, water',
},
],
},
{
'category 2': [
{
name: 'Banana Split',
desc: 'Banana, ice cream, chocolat, topping',
allergens: 'nuts',
},
{
name: 'Mango Sticky Rice',
desc: 'Mango, rice, milk',
allergens: '',
},
],
},
]
const checkIfInputMatches = (input, desc) => input.toLowerCase().split(" ").some(o => desc.toLowerCase().includes(o))
const filterByInput = (data, input) => {
let finalResult = [];
data.forEach(d => {
let keys = Object.keys(d);
let values = Object.values(d);
finalResult = [...finalResult, ...values.map((obj, index) => {
let result = obj.filter(o => checkIfInputMatches(input, o.desc))
return {[keys[index]]: result}
})]
})
return finalResult
}
console.log(filterByInput(dataObj, 'JUI'))
console.log(filterByInput(dataObj, "orange"))
console.log(filterByInput(dataObj, "rice"))
console.log(filterByInput(dataObj, "Orange banana"))
Hope this helps.

react-select add all items from group by click on group title

react-select
let options = [
{
"label": "Group 1",
"options": [
{
"label": "Item1", "value": "1|1"
},
{
"label": "Item2", "value": "1|2"
},
{
"label": "Item3", "value": "1|3"
}
]
},
{
"label": "Group n",
"options": [
{
"label": "Item1", "value": "2|1"
},
{
"label": "Item2", "value": "2|2"
}
]
}
];
<Select
onChange={this.onChange}
closeMenuOnSelect={false}
isMulti
menuIsOpen={true}
options={options}
/>
onChange is fired only for subitems of Group, in documentation I don't find property to set group title clickable or other options that would accomplish this, any ideas?
I also tried to add value for groups like this
[
{
"label": "Group 1",
"value": [
"1|1",
"1|2",
"1|3"
],
"options": [
{
"label": "item1",
"value": "1|1"
},
{
"label": "item2",
"value": "1|2"
},
{
"label": "item3",
"value": "1|3"
}
]
}
]
But group is not clickable
Yes, that's how react-select works, you can't directly be able to click/select on the group.
However, In order to make the group clickable, provide a label and value in an object inside options array.
Like this
let options = [
{
label: "---GROUP 1---",
value: "1"
},
{
options: [
{
label: "Item1",
value: "1|1"
...
This will make the group clickable and selectable.
Working copy of your code is in the codesandbox
Another info which might be useful to know is:
You can write custom component for the group. This way you can attach onClick or do or other kinds of stuff.
<Select
components={{GroupHeading: () => <div onClick={() => console.log('i am a group and i am clickable .. yay!')}>My Group Heading</div>}}
onChange={onChange}
====EDIT: Based on request in the comment:====
In order to select all group items upon clicking on group label, do the following:
maintain a state value for example
write a little custom component for the group label and attach an onClick which will concatenate the value(state)
use value prop of react-select and provide the value from state
write custom onChange and update the state i.e. value upon item select
I have updated the codesandbox working copy of code (same link as above)
Code Snippet:
const group1Options = [
{
label: "Item1",
value: "1|1"
},
{
label: "Item2",
value: "1|2"
},
{
label: "Item3",
value: "1|3"
}
];
const group2Options = [
{
label: "Item1",
value: "2|1"
},
{
label: "Item2",
value: "2|2"
}
];
const createGroup = (groupName, options, setValue) => {
return {
label: (() => {
return (
<div
onClick={() =>
setValue(value =>
value.concat(options.filter(grpOpt => !value.includes(grpOpt)))
)
}
>
{groupName}
</div>
);
})(),
options: options
};
};
export default function App() {
const [value, setValue] = useState([]);
let options = [
createGroup("---GROUP 1---", group1Options, setValue),
createGroup("---GROUP 2---", group2Options, setValue)
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Select
// components={{GroupHeading: () => <div onClick={() => console.log('i am a group and i am clickable .. yay!')}>My Group Heading</div>}}
onChange={option => {
console.log(option);
return setValue(option);
}}
closeMenuOnSelect={false}
isMulti
menuIsOpen={true}
options={options}
value={value}
/>
</div>
);
}

Resources