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