Map an object by looping through alphabet - javascript

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
}

Related

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!

Array.map / Filter a contigous array and then re-order it to be contiguous again

Using React, I have a list component that uses array.map to render a list of items.
The list items are variegated; every other list item has a different background color which depends on if the id field of the data structure that feeds the list item is even or odd:
...
const useStyles = makeStyles((theme) => ({
even: {
backgroundColor: theme.palette.background.paper,
},
odd: {
backgroundColor: "#c8c9c7",
},
}));
...
const classes = useStyles();
...
{!list || list.length < 1 ? (
<p>You have no assets selected...</p>
) : (
list.map((items) => (
<ListItem
className={items.id % 2 === 0 ? classes.even : classes.odd}
key={items.id}
>
...
/>
</ListItem>
))
)}
Here is an example of the data structure it uses:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":1,
"foo":"This is also a bar"
},
{
"id":2,
"foo":"Yes, this too, is a bar"
}
}
I need to remove items. Normal javascript.filter produces non contiguous ids as expected:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":2,
"foo":"Yes, this too, is a bar"
}
}
I need them to be contiguous:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":1,
"foo":"Yes, this too, is a bar"
}
}
I have a function that does what I need that needs some tweaking:
const handleRemoveAsset = (id) => {
const arrayCopy = [...assetListItems];
const filteredArray = arrayCopy
.filter((item) => item.id !== id)
for (var i=0; i < filteredArray.length; i++) {
filteredArray[i].id = i;
}
setAssetListItems(filteredArray);
};
This works, but one does not simply for loop using React... I am hoping to use filter and/or map for the entirety of this and not use the for loop that I have.
I read that you can chain filter and map and tried it but couldn't quite work it out. I came up with this:
const filteredArray = array
.filter((item) => item.id !== id)
.map((item, index) => {
item && item.id ? item.id : index)});
... which fails to compile with - expected an assignment to a function call and instead saw an expression on the line after .map.
Any advice at this point would appreciated, thank you!
You could chain map and filter and return the new object from map which updates the pre-existing id.
[...assetListItems]
.filter(item => item.id !== id)
.map((item, index) => ({
...item,
id: index,
}));
I just considered another scenario where if the id is not starting with 0. And if you want the starting id in the resultant array to be as the id of the first object then this is just another way of achieving the expected output.
let data = [{id:0, foo:'This is a bar'},{id:1, foo:'This is also a bar'},{id:2, foo:'Yes, this too, is a bar'}];
const filterItems = (items, id) => {
let lastPushedId = items[0]?.id;
return items.filter(item => item.id !== id).map(item => ({
...item,
id: lastPushedId++
}))
}
console.log(filterItems(data, 1));
//`id` of the first object is `3`
data = [{id:3, foo:'This is a bar'},{id:4, foo:'This is also a bar'},{id:5, foo:'Yes, this too, is a bar'}];
console.log(filterItems(data, 3));
.as-console-wrapper {
max-height: 100% !important;
}

Unexpected mutation of array in React

I am just learning to program and am writing one of my first applications in React. I am having trouble with an unexpected mutation that I cannot find the roots of. The snippet is part of a functional component and is as follows:
const players = props.gameList[gameIndex].players.map((item, index) => {
const readyPlayer = [];
props.gameList[gameIndex].players.forEach(item => {
readyPlayer.push({
id: item.id,
name: item.name,
ready: item.ready
})
})
console.log(readyPlayer);
readyPlayer[index].test = "test";
console.log(readyPlayer);
return (
<li key={item.id}>
{/* not relevant to the question */}
</li>
)
})
Now the problem is that readyPlayer seems to be mutated before it is supposed to. Both console.log's read the same exact thing. That is the array with the object inside having the test key as "test". forEach does not mutate the original array, and all the key values, that is id, name and ready, are primitives being either boolean or string. I am also not implementing any asynchronous actions here, so why do I get such an output? Any help would be greatly appreciated.
Below is the entire component for reference in its original composition ( here also the test key is replaced with the actual key I was needing, but the problem persists either way.
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
// import styles from './Lobby.module.css';
const Lobby = ( props ) => {
const gameIndex = props.gameList.findIndex(item => item.id === props.current.id);
const isHost = props.gameList[gameIndex].hostId === props.user.uid;
const players = props.gameList[gameIndex].players.map((item, index) => {
const isPlayer = item.id === props.user.uid;
const withoutPlayer = [...props.gameList[gameIndex].players];
withoutPlayer.splice(index, 1);
const readyPlayer = [];
props.gameList[gameIndex].players.forEach(item => {
readyPlayer.push({
id: item.id,
name: item.name,
ready: item.ready
})
})
const isReady = readyPlayer[index].ready;
console.log(readyPlayer);
console.log(!isReady);
readyPlayer[index].ready = !isReady;
console.log(readyPlayer);
return (
<li key={item.id}>
{isHost && index !== 0 && <button onClick={() => props.updatePlayers(props.gameList[gameIndex].id, withoutPlayer)}>Kick Player</button>}
<p>{item.name}</p>
{isPlayer && <button onClick={() =>props.updatePlayers(props.gameList[gameIndex].id, readyPlayer)}>Ready</button>}
</li>
)
})
let showStart = props.gameList[gameIndex].players.length >= 2;
props.gameList[gameIndex].players.forEach(item => {
if (item.ready === false) {
showStart = false;
}
})
console.log(showStart);
return (
<main>
<div>
{showStart && <Link to="/gameboard" onClick={props.start}>Start Game</Link>}
<Link to="/main-menu">Go back to Main Menu</Link>
</div>
<div>
<h3>Players: {props.gameList[gameIndex].players.length}/4</h3>
{players}
</div>
</main>
);
}
Lobby.propTypes = {
start: PropTypes.func.isRequired,
current: PropTypes.object.isRequired,
gameList: PropTypes.array.isRequired,
updatePlayers: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
}
export default Lobby;
Note: I did manage to make the component actually do what it is supposed, but the aforementioned unexpected mutation persists and is still a mystery to me.
I have created a basic working example using the code snippet you provided. Both console.log statements return a different value here. The first one returns readyPlayer.test as undefined, the second one as "test". Are you certain that the issue happens within your code snippet? Or am I missing something?
(Note: This answer should be a comment, but I am unable to create a code snippet in comments.)
const players = [
{
id: 0,
name: "John",
ready: false,
},
{
id: 1,
name: "Jack",
ready: false,
},
{
id: 2,
name: "Eric",
ready: false
}
];
players.map((player, index) => {
const readyPlayer = [];
players.forEach((item)=> {
readyPlayer.push({
id: item.id,
name: item.name,
ready: item.ready
});
});
// console.log(`${index}:${readyPlayer[index].test}`);
readyPlayer[index].test = "test";
// console.log(`${index}:${readyPlayer[index].test}`);
});
console.log(players)

Search filter won't accept "Round" for one value because "Ground" is a value in every result

I am working on a photo gallery for swimming pools. The search bar updates state per character entered, similar to the Netflix search bar. It updates on all keys in the JSON file, similar to how you can search for either "Good Will Hunting" as a title or "Matt Damon" as an actor on Netflix.
The issue I am running into is that "round" will be a common search term, triggering "shape":"round" in the JSON. But another category is "type": "above-ground" or "type":"in-ground".
As a result, if you search for a round pool, the query doesn't filter to show only round pools because "round" is also triggering the word "ground". It shows a mish-mash of round pools and any pool that triggers "above-ground" or "in-ground".
I have tried messing around with the .filter and .includes methods to no avail. I'm not sure if I'm missing something there or what.
I'm also about to look into regular expressions to maybe dictate "if the search term "round" does not have a preceding character, show round pools and ignore the ground trigger".
import React, { Component } from "react";
class App extends Component {
state = {
filter: "",
data: [
{
shape: "Round",
type: "Above-ground"
},
{
shape: "Rectangle",
type: "In-ground"
},
{
shape: "Kidney",
type: "In-ground"
}
]
};
handleChange = event => {
this.setState({
filter: event.target.value
});
};
render() {
const { filter, data } = this.state;
const lowerCasedFilter = filter.toLowerCase();
const filteredData = data.filter(item => {
return Object.keys(item).some(key =>
item[key].toLowerCase().includes(lowerCasedFilter)
);
});
return (
<div>
<input value={filter} onChange={this.handleChange} />
{filteredData.map(item => (
<div>
<p>Shape: {item.shape}</p>
<p>Type: {item.type}</p>
<hr />
</div>
))}
</div>
);
}
}
export default App;
I expect for the search filter to only show "round" pools when the user enters "round", and not show excess results due to the word "ground"
You could utilize a dynamic RegExp that uses word boundaries \b for "complete word" matches.
const lowerCasedFilter = filter.toLowerCase();
const filteredData = data.filter(item => {
return Object.keys(item).some(key => {
const stringToSearch = item[key].toLowerCase();
const regex = new RegExp("\\b"+ lowerCasedFilter +"\\b"); // build regex around the given search term ("round" in your case)
return regex.test(stringToSearch); // use "test" to see if your term is present in the property value
});
});
Relevant information in this post: whole word match in javascript.
find if one word starts like lowerCasedFilter in any field of data objects
item[key].toLowerCase().split(' ').some(word => word.slice(0, lowerCasedFilter.length) === lowerCasedFilter)
const data = [
{
shape: "Round",
type: "Above-ground"
},
{
shape: "Rectangle",
type: "In-ground"
},
{
shape: "Kidney",
type: "In-ground"
}
]
filteredData = (lowerCasedFilter) => data.filter(item => {
return Object.keys(item).some(key =>
item[key].toLowerCase().split(' ').some(word => word.slice(0, lowerCasedFilter.length) === lowerCasedFilter)
);
});
console.log( filteredData('round') )
If it's specific to the five letters "round", you could just use
const filteredData = lowerCasedFilter === "round" ?
data.filter(item => item.shape === "Round") :
data.filter(item => {
return Object.keys(item).some(key =>
item[key].toLowerCase().includes(lowerCasedFilter)
);
});

How to map over an object which has a unique id for each object values and return the values to a React component

I am working on a redux project where I want to retrieve the values stored in API server.I want to store the data from the API in my redux store and then retrieve the values and display it in my react component.The data in the API server is in the form of an object but has a unique id for each value.So,in my case, the data is a list of posts.So, each post has a unique id and has all the other details like timestamp,post-title,post-author etc.This is how the default data from the API for posts looks like:
const defaultData = {
"8xf0y6ziyjabvozdd253nd": {
id: '8xf0y6ziyjabvozdd253nd',
timestamp: 1467166872634,
title: 'Udacity is the best place to learn React',
body: 'Everyone says so after all.',
author: 'thingtwo',
category: 'react',
voteScore: 6,
deleted: false,
commentCount: 2
},
"6ni6ok3ym7mf1p33lnez": {
id: '6ni6ok3ym7mf1p33lnez',
timestamp: 1468479767190,
title: 'Learn Redux in 10 minutes!',
body: 'Just kidding. It takes more than 10 minutes to learn technology.',
author: 'thingone',
category: 'redux',
voteScore: -5,
deleted: false,
commentCount: 0
}
}
Note: The "id" which is a random number here(like "8xf0y6ziyjabvozdd253nd") becomes an integer, i.e the 1st post gets the id 1 ,the 2nd gets 2.
So, I am able to store the data from posts API in my redux "Store". And I converted the posts object into an array(as we cannot map over an object) because I want to map over this array and show the data in my React component.But,I am not able to see the result of the array in my component,maybe that's because it has an id before each object in the array.This is how I am trying to map over the arrayand I do not get any error,but I do not see the results from the object in component.My component file looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions';
import _ from 'lodash';
class PostsIndex extends Component {
componentDidMount() {
this.props.dispatch(fetchPosts())
.then(() => {
this.setState({
loading : false
});
// console.log(this.props.posts.posts[0])
})
}
render() {
// console.log(this.props.posts.posts)
const obj = this.props.posts.posts;
let arr;
if (obj) {
arr = Object.values(obj); //Converting an Object into an array
}
console.log(arr); //returns the converted array from an object
return(
<div>
{
arr ?
<div>
{ arr.map(post =>
{
<div>
{post.title}
</div>
})
}
</div>
:
<div>
No Data
</div>
}
</div>
);
}
}
function mapStateToProps(state) {
return { posts: state.posts };
}
export default connect(mapStateToProps)(PostsIndex);
And when I console.log my state,the converted array looks like this:
When expanded looks like:
I want to retrieve almost all of the values from the above array.Can anyone please show me how to get the data from the array and map over the array to show the values in my React component?
You can keep your data as an object, and use Object.keys to get an array of keys, map over the keys, and use the keys to access nested objects. If you don't know the shape of your data source, you could use recursion.
Here is a working code sandbox example where I've taken your data source and turned it into a table to illustrate how to do so.
Here is the code:
import React from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
const defaultData = {
"8xf0y6ziyjabvozdd253nd": {
id: '8xf0y6ziyjabvozdd253nd',
timestamp: 1467166872634,
title: 'Udacity is the best place to learn React',
body: 'Everyone says so after all.',
author: 'thingtwo',
category: 'react',
voteScore: 6,
deleted: false,
commentCount: 2
},
"6ni6ok3ym7mf1p33lnez": {
id: '6ni6ok3ym7mf1p33lnez',
timestamp: 1468479767190,
title: 'Learn Redux in 10 minutes!',
body: 'Just kidding. It takes more than 10 minutes to learn technology.',
author: 'thingone',
category: 'redux',
voteScore: -5,
deleted: false,
commentCount: 0
}
}
const TableHeader = ({ fields }) => (
<thead>
<tr>
{
fields.map( field => <th key={ field }>{ field }</th>)
}
</tr>
</thead>
);
const TableBody = ({ data }) => (
<tbody>
{
Object.keys(data).map(
datum =>
<tr>
{
Object.keys(data[datum]).map(
field => <td>{data[datum][field]}</td>
)
}
</tr>
)
}
</tbody>
);
const App = () => (
<table>
<TableHeader fields={ Object.keys(defaultData[Object.keys(defaultData)[0]]) } />
<TableBody data={defaultData} />
</table>
);
render(<App />, document.getElementById('root'));
You need map your data. Something like this:
<div>
<ul>
{props.incomingDataArray.map((singleArrayItem, index) => {
return (
<li key="list + index">{singleArrayItem.commentCount}</li>
<li key="list + index">{singleArrayItem.timestamp}</li>
)
})
}
</ul>
<div>
you forgot return inside your map.
render() {
const obj = this.props.posts.posts;
let arr;
if (obj) {
arr = Object.values(obj); //Converting an Object into an array
}
return (
<div>
{ arr ?
<div>
{ arr.map(post => {
return (
<div key={post.id}>
{post.title}
</div>
)
})
}
</div> :
<div>
No Data
</div>
}
</div>
);
}
or, alternatively, drop the curly braces with a oneliner:
arr.map(post => (<div key={post.id}>
{post.title}
</div>
)
)
and don't forget the key attribute.
I also suggest you abstract way your dumb components. It'll make your code more readable.

Categories