Sort mapped list with Lodash on ReactJS - javascript

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");

Related

React Hook Form move items between useFieldArray lists

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.

How can I access array items inside an object

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

How to not show the first item on a array in a map?

I receive an array like this from backend:
[
{
id: 0,
name: "John",
language: "Enlgish"
},
{
id: 1,
name: "Chris",
language: "Spanish"
},
{
id: 2,
name: "Bastian",
language: "German"
}
]
So I display the languages from this array in a table, and to do that I map through them.
I don't want to show the first language on the first object of this array
Parent.js
const [language, setLanguage] = useState ([])
useEffect(() => {
axios
.get('api').then((res) => {setLanguage(response.data.languages)})
}, [])
Child.js
return(
{language.map((lang, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={
lang.language
? lang.language.shift()
: lang.language
}
</div>
</td>
</tr>
))}
)
So what I have tried by far is the shift method which removes the first item of an array, but it didn't work.
This error happened :TypeError: lang.language.shift is not a function
How can I fix this?
Use the index
{language.map((lang, i) => {
(i > 0) && (
return (
......

how to use map in react to create multiple component?

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!

reuse react component in a map function

I have doubt I'm doing it right when returning a component in a map iteration. How can I improve the code or is there any better way to do it? (although my code is working)
https://codesandbox.io/s/5yzqy6vyqx
Parent
function App() {
return (
<div className="App">
{[
{
name: "banana",
count: 3
},
{
name: "apple",
count: 5
}
].map(({ name, count }) => {
return <List name={name} count={count} />;
})}
</div>
);
}
List component
const List = ({ name, count }) => {
return (
<li>
{name}: {count}
</li>
);
};
Simplify like this.
function App() {
return (
<div className="App">
<ul>
{[
{
name: "banana",
count: 3
},
{
name: "apple",
count: 5
}
].map(({ name, count }) => <li>{name}:{count} </li>)}
</ul>
</div>
);
}
You need to set unique value as key to List component because you are rendering List in loop. Unique value can be id per each object in array or index from .map but we are recommended to have unique id per object in data and use that as key when we iterate.
Index is not recommended as key but in worst case we can.
Also add ul element so that li will be rendered under ul
Below code has improved with adding key, ul and .map function without return
function App() {
return (
<div className="App">
<ul>
{[
{
id: 1
name: "banana",
count: 3
},
{
id:2,
name: "apple",
count: 5
}
].map(({ id, name, count }) => (
<List key={id} name={name} count={count} />;
))}
</ul>
</div>
);
}

Categories