I have an array which contains multiple objects (which have their own array of objects inside them). What I'm trying to do is map through each object's "subMenuItems" array and output the values inside.
Here's my Array:
export const MenuItems = [
{
category: 'Buy',
subMenuItems: [
{
title: `Watching`,
link: '/watching',
},
{
title: `Bids & Offers`,
link: '/bids-offers',
},
{
title: `Bought`,
link: '/bought',
},
],
},
{
category: 'Sell',
subMenuItems: [
{
title: `Selling`,
link: '/selling',
},
{
title: `Sold`,
link: '/sold',
},
{
title: `Sell with us`,
link: '/sell-with-us',
},
],
},
];
I'm able to get each object's category no problem by doing this:
const listItems = MenuItems.map((item, i) => (
<li key={i}>{item.category}</li>
));
Render:
return(
<ul>{listItems}</ul>
)
However I can't seem to get the syntax right when trying to access the "subMenuItems" in each object.
My end goal is to have a function that returns each category with it's sub menu items below it:
E.G.
Buy
- Watching
- Bids & offers
- Bought
Sell
- Selling
- Sold
- Sell With Us
Hope that makes sense :) Thanks!
You can do the same thing and render them using .map:
const listItems = MenuItems.map((item, i) => (
<li key={i}>
{item.category}
<ul>
{item.subMenuItems?.map((index, subMenu) => (
<li key={subMenu.title}> // index as key can be problematic
<a href={subMenu.link}>{subMenu.title}</a>
</li>
)}
</ul>
</li>
));
You could map the subMenuItems into a ul and add it to your outer li
const listItems = MenuItems.map((item, i) => (
const itemlist = item.subMenuItems.map((a, b) => (
<li key={a.title}><a href={a.link}>{a.title}</a></li>
));
<li key={i}>{item.category}<ul>{itemlist}</ul></li>
));
Related
I'm using React Hook Form to build a basic page builder application and it's been brilliant so far, I've been using the useFieldArray hook to create lists that contain items, however, I haven't found a way to move items between lists.
I know I can use the move() function to reorder items within the same list, however, since each list has its own nested useFieldArray I can't move the item from one list component to another list component.
If anyone knows of a way around this it would be much appreciated!
Here is a very simplified example of my current setup:
export const App = () => {
const methods = useForm({
defaultValues: {
lists: [
{
list_id: 1,
items: [
{
item_id: 1,
name: 'Apple'
},
{
item_id: 2,
name: 'Orange'
}
]
},
{
list_id: 2,
items: [
{
item_id: 3,
name: 'Banana'
},
{
item_id: 4,
name: 'Lemon'
}
]
}
]
}
});
return (
<FormProvider {...methods}>
<Page/>
</FormProvider>
)
}
export const Page = () => {
const { control } = useFormContext();
const { fields } = useFieldArray({
control,
name: 'lists'
})
return (
<ul>
{fields?.map((field, index) => (
<List listIdx={index} />
))}
</ul>
)
}
export const List = ({ listIdx }) => {
const { control, watch } = useFormContext();
const { fields, move } = useFieldArray({
control,
name: `lists[${sectionIdx}].items`
})
const handleMove = (prevIdx, nextIdx) => {
// this allows me to move within lists but not between them
move(prevIdx, nextIdx);
}
return (
<li>
<p>ID: {watch(lists[${listIdx}].list_id)}</p>
<ul>
{fields?.map((field, index) => (
<Item listIdx={index} itemIdx={index} handleMove={handleMove}/>
))}
</ul>
</li>
)
}
export const Item = ({ listIdx, itemIdx, handleMove }) => {
const { control, register } = useFormContext();
return (
<li>
<p>ID: {watch(lists[${listIdx}].items[${itemIdx}].item_id)}</p>
<label
Name:
<input { ...register('lists[${listIdx}].items[${itemIdx}]) }/>
/>
<button onClick={() => handleMove(itemIdx, itemIdx - 1)}>Up</button>
<button onClick={() => handleMove(itemIdx, itemIdx + 1)}>Down</button>
</div>
)
}
Thanks in advance!
If you'd not like to alter your default values (your data structure), I think the best way to handle this is using update method returning from useFieldArray. You have the data of both inputs that are going to be moved around, knowing their list index and item index, you could easily update their current positions with each other's data.
I have this array that has this structure please check the code down below , my end results should look like the following :
veg
apple
carrot
meat
chicken
steak
my current results are
apple
carrot
chicken
steak
since I cannot structure the array other wise and don't want to go in to deep nesting or even nest loops which I doubt it will work in react any idea how to achieve the previous results using map , where I map through group only once to create the group name and to then add the items related to that group inside that group?, food for thought : could conditional rendering be also leveraged here ?
I was able to only get either the group multiple times or the items only..
const arr = {
itmes: [
{ id: 1, group: "veg", item: "apple" },
{ id: 2, group: "veg", item: "carrot" },
{ id: 3, group: "meat", item: "chicken" },
{ id: 4, group: "meat", item: "steak" }
]
};
function App() {
return (
<div>
{arr["itmes"].map(
(item) => item.group
//item.item
)}
</div>
);
}
Codesanadbox
You should wrap the items first and render the grouped ones
const groupItems = items =>
items.reduce((groupedItems, item) => {
if (!groupedItems[item.group]) {
groupedItems[item.group] = []
}
groupedItems[item.group].push(item)
return groupedItems
}, {})
const items = Object.entries(groupItems(arr.items)).map(
([groupName, items]) => (
<React.Fragment>
<li>{groupName}</li>
{items.map(item => (
<li>{item.item}</li>
))}
</React.Fragment>
)
)
Option 1
First, make sure your array is sorted by Group:
const sorted = arr["itmes"]
.sort((a, b) => (a.group || '').localeCompare(b.group));
Then you can render and conditionally add another heading element whenever the group name changes:
<ul>
{data.map((d, id) => (
<>
((id > 0 || d.group !== data[id - 1].group) ? <li key={`${id}-h`}><b>{d.group}</b></li> : undefined)
<li key={`${id}-v`}>{d.item}</li>
</>
))}
</ul>
Extra: Custom group sorting
If you need to custom sort the array according to another array:
const sortLtu = ['veg', 'apple', 'meat'];
data.sort((a, b) => sortLtu.indexOf(a.group) - sortLtu.indexOf(b.group));
Option 2: Util function
If you end u doing this often you may create a util function:
Array.prototype.groupBy = function(cb) {
const groups = [];
this.forEach((d, id, arr) => {
const g = cb(d, id, arr);
let group = groups.find(_g => _g.group === g);
if (!group) {
group = { group: g, items: [] };
groups.push(group);
}
group.items.push(d);
})
return groups;
}
And then use it like
{data.groupBy(i => i.group).map((bundle, ix) => (
<div key={ix}>
<b>{bundle.group}</b>
<ul>
{bundle.items.map((item, ix) => <li key={ix}>{item.item}</li>)}
</ul>
</div>
))}
Im very new to javascript, but like hgb123's answer, something like this inside a render or return block:
<div className="myClass">
{myList.map((eachItemInList) => (
<ReactComponent certainProp={eachItemInList} />
))}
</div>
works perfectly for a list like this one:
const myList = ['item1', 'item2', 'item3', 'item4', 'item5', 'item6']
hope this helped someone!
I want to be able to loop through each letter of the alphabet and then if the StoreName in my json object matches that looped letter then map it to an li tag. I want to do this to every letter in the alphabet and if there is no match just display nothing.
Hope this makes sense.
for example:
This is what I have so far.
import { data } from './data/data';
import { useState } from 'react';
export default function Home() {
const [values, setValues] = useState(data);
return (
<div>
{values.filter(store => store.storeName.startsWith('a'))
.map((item, index) => (
<li key={index}>
{item.storeName}
</li>
))}
</div>
)
}
Json object:
export const data = [
{
storeId: 1,
storeName: 'addidas',
},
{
storeId: 2,
storeName: 'axels',
},
{
storeId: 3,
storeName: 'blix',
},
{
storeId: 4,
storeName: 'benis',
},
{
storeId: 5,
storeName: 'clives',
},
];
I know i could filter and map each letter manually but there must be a way to loop through the alphabet and map?
example output:
A
Addidas
axels
B
blix
benis
c
clives
d
So I want the letter to display and then the results for each item thats been looped that starts with that looped letter.
You could make a list of alphabet and map through that
import { data } from "./data/data";
import React, { useState } from "react";
const alphabet = "abcdefghijklmnopqrstuvwxyz";
export default function Home() {
const [values, setValues] = useState(data);
return (
<div>
{alphabet.split("").map((c) => {
return (
<>
<p>{c}</p>
{values
.filter((store) => store.storeName.startsWith(c))
.map((item, index) => (
<li key={index}>{item.storeName}</li>
))}
</>
);
})}
</div>
);
}
It sounds like you want to group your datapoints by initial letter. The algorithm you describe will work, but it's not the one I'd choose.
Here's how I would do it:
Sort all stores alphabetically
Iterate through the sorted stores, keeping track of the most recent initial letter
every time the current store's initial letter is different from the most recent, insert a grouping boundary (e.g. closing the previous <li> and opening a new one)
Finally, don't implement this within the JSX. It may not be the most complex algorithm in the world, but nobody will appreciate it if this data-preparation logic is intermixed with a bunch of display literals. Instead, work with the data as pure data. Bearing that in mind, "inserting a grouping boundary" just means changing which group you insert the store into.
Here's a run at the implementation. (I haven't tested it.)
function MyComponent( props ) {
// group the stores by initial letter
// produces a list of { initialLetter: 'A', stores: [ store1, store2, store3 ] }
let prevInitialLetter = null
let groupedStores = stores
.sort(sort_storeName_alpha)
.reduce(( groups, store ) => {
let myInitialLetter = store.storeName.charAt(0)
let belongsInNewGroup = myInitialLetter !== prevInitialLetter
if(belongsInNewGroup) {
// create a new group and add this store as its first member
groups.push({ initialLetter: myInitialLetter, stores: [ store ] })
} else {
// add this store to the previous group
groups[groups.length - 1].stores.push(store)
}
return groups
}, [])
return (
<ol className="GroupedStores">
{
groupedStores.map(group => (
<li className="storeGroup" key={group.initialLetter}>
{group.initialLetter}
<ol className="stores">
{
group.stores.map(store => (
<li key={store.storeName}>
{store.storeName}
</li>
)
}
</ol>
</li>
))
}
</ol>
)
}
// this is the crudest-possible text sort; depending on your needs,
// you may need to perform a locale-aware comparison
function sort_storeName_alpha = ( a, b ) => {
if(a.storeName < b.storeName) return -1
if(b.storeName < a.storeName) return 1
return 0
}
I have an array of objects like this getting mapped and rendered.
const items = [
{
id: '1',
name: 'name',
comment: 'text',
},
{
id: '2',
name: 'name',
},
etc...
]
var hasComment = items.some(item => item.hasOwnProperty('comment'));
const card = items.map(item =>
<div key={item.id}>
<ul className="detail">
<li>{item.name}</li>
{hasComment ? <li>{item.comment}</li> : ''}
</ul>
</div>
I want the comment to be displayed depending of the property of each individual object. I googled the above solution but as soon as any object of my array has this property it gets displayed for all, leaving some empty list items. The comment on each individual item should only be rendered, if the object actually has this property.
Hopefully it's clear what I mean.
You need to put the property check inside a .filter before mapping to render:
const card = items
.filter(item => item.hasOwnProperty('comment')
.map(item => {
// ...
I have an array:
people = [
{
name: "Kyle",
_id: "2"
},
{
name: Susan,
_id: "1"
}
]
I have a mapped list as such:
return (
<ul>
{people.map( (person) => {
return (
<li>
<span>{person.name}</span>
<span>{person.id}</span>
</li>
)
})}
</ul>
)
This is all working well, but how can I sort them, say based on "name"? I tried to use the [_.sortBy][1] but couldn't figure out how it would fit to the context of React and map function. Using lodash, or underscore's _.sortBy would be appreciated.
Thanks!
In _.sortBy you can pass the name of the field that will be used for sorting your collection, read more about _.sortBy
var Component = React.createClass({
getInitialState() {
return {
people: _.sortBy([
{ name: "Kyle", _id: 2},
{ name: "Susan", _id: 1}
], '_id')
}
},
sortBy(field) {
this.setState({
people: _.sortBy(this.state.people, field)
});
},
render: function() {
var peopleList = this.state.people.map( (person, index) => {
return (<li key={index}>
<span>{person.name}</span>
<span>{person.id}</span>
</li>);
})
return <div>
<a onClick={this.sortBy.bind(this, '_id')}>Sort By Id</a> |
<a onClick={this.sortBy.bind(this, 'name')}>Sort By Name</a>
<ul>{peopleList}</ul>
</div>;
}
});
Example
You can use the sortBy like this:
_.sortBy(<array name here>, '<key here>')
Try
return (
<ul>
{_.sortBy(people, 'name').map( (person) => {
return (
<li>
<span>{person.name}</span>
<span>{person.id}</span>
</li>
)
})}
</ul>
)
You need to import it first
import sortBy from "lodash/sortBy";
And then you can use it like
const arr = [{id: 2}, {id: 0}];
sortBy(arr, "id");