Don't output same items from array - javascript

I'm using react js. I try to map an array with objects and to output the name of each object from array:
const arr = [
{
name:"Bill",
age:88
},
{
name:"Bill",
age:18
},
{
name:"Jack",
age:55
},
]
{arr.map(i => (
<li key={i.id}>
{i.name}
</li>
))}
I want to avoid the same name when i do {i.name}. For this i made this:
{new Set(i.name)}
.. but it does not help. How to avoid displaying the same name in map function?

You need to create a Set of names before mapping and rendering. You can do it like below
{[...new Set(arr.map(i => i.name))].map(i => (
<li key={i.id}>
{i.name}
</li>
))}

you can use lodash to overcome this issue. Following code snippet will easily do what you need.
_.uniqBy(arr,"name").map(i => (
<li key={i.id}>
{i.name}
</li>
))}

You can remove the duplicates from the arr based on the name property in each object and then use array map() method on it like:
{[...new Map(arr.map(i=> [i.name, i])).values()].map(i => (
<li key={i.id}>
{i.name}
</li>
))}
Demo:
const arr = [{name:"Bill",age:88},{name:"Bill",age:18},{name:"Jack",age:55}];
class App extends React.Component {
render() {
return (<div>
{[...new Map(arr.map(i=> [i.name, i])).values()].map(i => (
<li key={i.id}>
{i.name}
</li>
))}
</div>);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

Cannot access updated value

Hello Am i need of some assistance here am stuck,tried to serach for solution on SO but cant find a solution ,Am learning react so decided to create a todo app.However i have been stuck when it comes to crossing off completed tasks. when i add a task i have a variable called tasks which basically is an object containing all the tasks in the following fomart:
enter image description here
Inside my app i have the following snippet of code
const FILTER_MAP = {
all_items:() => true,
Active: task => !task.completed,
Completed: task => task.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP);
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
const [filter, setFilter] = useState('all_items');
function addTask(name) {
const newTask = {id: "todo-" + nanoid(),name: name, completed:false}
setTasks([...tasks, newTask]);
}
function toggleTaskCompleted(id) {
const updatedTasks = tasks.map(task => {
// if this task has the same ID as the edited task
if(id === task.id) {
// use object spread to make a new object
// whose `completed` prop has been inverted
return {...task,completed: !task.completed}
}
return task;
})
setTasks(updatedTasks);
}
function clearCompletedTasks(){
const completed = tasks.filter(task => task.completed === false)
setTasks(completed);
}
const taskList = tasks
.filter(FILTER_MAP[filter])
.map(task => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/>
));
const filterList = FILTER_NAMES.map(name => (
<FilterButton
key={name}
name={name}
isPressed={name === filter}
setFilter={setFilter}
/>
));
const tasksNoun = taskList.length !== 1 ? 'items' : 'items';
const headingText = `${taskList.length} ${tasksNoun} left`;
return (
<div>
<header>
<h1 id="pageTitle">Todo</h1>
<div className="container">
<div className="main">
<section className="tasklist">
<Form addTask={addTask}/>
<ul className="listItems">
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{taskList}</li>
</ul>
</section>
</div>
<div className="footer">
<div className="footer-list">
<ul>
<li id="items">{headingText}</li>
<li id="all-items">{filterList[0]}</li>
<li id="active">{filterList[1]}</li>
<li id="completed">{filterList[2]}</li>
<li id="clear" onClick={clearCompletedTasks}>XClear Completed</li>
</ul>
</div>
</div>
</div>
</header>
</div>
);
}
export default App;
Todo
export default function Todo(props){
return (
<li>
<div className="todo">
<label htmlFor={props.id}>
{props.name}
</label>
<input id={props.id}
type="checkbox"
defaultChecked={props.completed}
onChange={() =>
props.toggleTaskCompleted(props.id)}
/>
</div>
</li>
);
}
Problem
When i click on checkbox to indicate the task is done i can see that the value in completed is updating to true as show below
enter image description here
However when i try to evaluate and apply the following css its not working.
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{taskList}</li>
How can i implement this so that "completed" style class is used when the state of completed value changes to true.Thank you
The problem is that you use a const to store an array which gets changed.
The correct thing to do would be the following:
<ul>{tasks
.filter(FILTER_MAP[filter])
.map(task => (
<li class={`todo-item ${ task.completed ? "completed" :'' }`}>
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/></li>
))}
</ul>
This looks like a case of stale state. When your function closes over an old state (think closures), then whenever called in future it has access to the old state itself. Similar question
Check if moving your {tasksList} code inside the return method helps:
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{
tasks
.filter(FILTER_MAP[filter])
.map(task => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/>
));}</li>

Could there have been a better way of coding this? (Beginner) React.js [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
This is what I've been taught how to map through arrays with lists. I was wondering if there would be a better way to improve this by keeping it DRY.
I know I could make a single array with objects, however, it's for 3 different columns on a page.
If I were to make changes in the future and maintain the list, would this be sufficient enough?
const groceryList = [
{ Dairy: "Eggs"},
{ Dairy: "Milk"},
{ Dairy: "Cheese"},
];
const shoppingList = [
{ toBuy: "Soap"},
{ toBuy: "Garbage bags"},
];
const getHomeDepot = [
{ getTools: "Plywood"},
{ getTools: "Nails"},
]
{groceryList &&
groceryList.map((ingredients) => {
return (
<ul>
<li>
{ingredients.Dairy}
</li>
</ul>
);
{shoppingList &&
shoppingList.map((items) => {
return (
<ul>
<li>
{items.ToBuy}
</li>
</ul>
);
{getHomeDepot &&
getHomeDepot.map((tools) => {
return (
<ul>
<li>
{tools.getTools}
</li>
</ul>
);
I would make the list into an object, and the different lists into keys. This way, the lists will be easy to add to as they are arrays and you can add list types as keys to the object as needed.
const lists = {
dairy: ["Eggs", "Milk", "Cheese"],
buy: ["Soap", "Garbage Bags"],
tools: ["Plywood", "Nails"]
};
As far as populating the columns... there should only be 1 <ul> surrounding the list items, so this should be outside of the map method. To be dry, you can make a method like this:
const populateListItems = (list) => {
return list.map((listItem) => (
<li>{listItem}</li>
))
};
and then use it to populate your column list items like so:
<div class="col-1">
{list.dairy.length && (
<ul>
{populateListItems(list.dairy)}
</ul>
)}
</div>
<div class="col-2">
{list.buy.length && (
<ul>
{populateListItems(list.buy)}
</ul>
)}
</div>
<div class="col-3">
{list.tools.length && (
<ul>
{populateListItems(list.tools)}
</ul>
)}
</div>
To make this code even more concise, you can use Object.keys to create as many columns as you need dependent on how many list keys are in the "list" object. This will return the same thing as above.
Object.keys(list).map((key, index) => (
<div class=`col-${index}`>
<ul>
{populateListItems(list[key])}
</ul>
</div>
));
I would take a cue from React-Native's FlatList component. At a minimum you want a component that can render an array of items using a renderItem function and can set mapped keys.
const FlatList = ({
data = [],
renderItem,
getKey = (_, i) => i // <-- return array index by default
}) =>
data.map((el, index) => (
<React.Fragment key={getKey(el, index)}>
{renderItem(el, index)}
</React.Fragment>
));
const groceryList = [{ Dairy: "Eggs" }, { Dairy: "Milk" }, { Dairy: "Cheese" }];
const shoppingList = [{ toBuy: "Soap" }, { toBuy: "Garbage bags" }];
const getHomeDepot = [{ getTools: "Plywood" }, { getTools: "Nails" }];
const primitives = ['Bill', 'Ted', 'Socrates'];
const FlatList = ({ data = [], renderItem, getKey = (_, i) => i }) =>
data.map((el, index) => (
<React.Fragment key={getKey(el, index)}>
{renderItem(el, index)}
</React.Fragment>
));
function App() {
return (
<div>
<FlatList
data={groceryList}
renderItem={(el) => <div>{el.Dairy}</div>}
/>
<ul>
<FlatList
data={shoppingList}
renderItem={(el) => <li>{el.toBuy}</li>}
/>
</ul>
<ol>
<FlatList
data={getHomeDepot}
getKey={(el, i) => el.getTools}
renderItem={(el) => <li>{el.getTools}</li>}
/>
</ol>
<ul>
<FlatList
data={primitives}
renderItem={(name, i) => <li>{i} : {name}</li>}
/>
</ul>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<App />,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Map over nested array of objects

I am trying to map over array of objects which each array contains another nested array of objects. However, the map does not work on the nested array. How do i map over the contents of the nested array?
data structure for nested array of objects looks like this:
const items = [
{
page_num: 1,
status: "PROCESSED",
table_text_data:[
{bottom:58, height:60, left:60, right:67, text:"recorded"},
{bottom:75, height:67, left:50, right:60, text:"symbolic"},
{bottom:80, height:80, left:77, right:89, text:"fever"},
]
}
];
map for page_num and status looks like this:
{this.props.items.map(item =>{
return (
<ul><li> page_num={item.page_num} </li>
<li> status:{item.status}</li>
{item.table_text_data.map((c,i)=>(
<ul><li>bottom={c.bottom}</li>
<li>height={c.height}</li>
<li>left={c.right}</li>
<li>right={c.left}</li>
<li>text={c.text}</li></ul>
))}
</ul>
)})}
page_num and status works fine but not for table_text_data. how should i map through it?
screenshot of the warning i'm getting as well:
https://i.stack.imgur.com/sqREQ.png
Any help would be much appreciated. Thank you
Why don't you use re-use array map?
{this.props.items.map((item, i) =>{
return (
<ul key={'parent' + i}><li> page_num={item.page_num} </li>
<li> status:{item.status}</li>
{item.table_text_data.map((c,j)=>(
<li key={'child' + j}>
<ul><li>bottom={c.bottom}</li>
<li>height={c.height}</li>
<li>left={c.right}</li>
<li>right={c.left}</li>
<li>text={c.text}</li></ul>
</li>
))}
</ul>
)})}
the error you give is has the answer in it.
Each child in a list should have a unique key prop
you're already halfway there by extracting the index that holds the nested object.
Now you only have to add the property key={I} to your ul
const items = [
{
page_num: 1,
status: "PROCESSED",
table_text_data:[
{bottom:58, height:60, left:60, right:67, text:"recorded"},
{bottom:75, height:67, left:50, right:60, text:"symbolic"},
{bottom:80, height:80, left:77, right:89, text:"fever"},
]
},
{
page_num: 2,
status: "PROCESSED",
table_text_data:[
{bottom:58, height:60, left:60, right:67, text:"recorded"},
{bottom:75, height:67, left:50, right:60, text:"symbolic"},
{bottom:80, height:80, left:77, right:89, text:"fever"},
]
}
];
const ItemDisplay = (props) => (
<React.Fragment>
{props.items.map(item => (
<ul key={item.page_num}>
<li>page_num={item.page_num}</li>
<li>status={item.status}</li>
{item.table_text_data.map((c,i) => (
<ul key={i}>
<li>bottom={c.bottom}</li>
<li>height={c.height}</li>
<li>left={c.right}</li>
<li>right={c.left}</li>
<li>text={c.text}</li>
</ul>
))}
</ul>
))}
</React.Fragment>
);
ReactDOM.render(
<ItemDisplay items={items} />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
you can use nested map function to acces the files. but you need to speciy that nested map() should work only for table_text_data property,other wise your parent map function will check for the contents inside table_text_data
you can rewrite the code like
{this.props.items.map(item =>{
return(
<ul>
<li> page_num={item.page_num} </li>
<li> status:{item.status}</li>
</ul>
if(item ==='table_text_data'){
{item.table_text_data.map((c,i)=>(
return (
<ul><li>bottom={c.bottom}</li>
<li>height={c.height}</li>
<li>left={c.right}</li>
<li>right={c.left}</li>
<li>text={c.text}</li></ul>
))
}
)}
</ul>
)})}

How to get data-attribute in React?

I have this list:
const chosen = (e: any) => console.log(e.target.dataset.value)
...
<ul>
{numbers.map(n => (
<a data-value={n} onClick={chosen}>
<li key={n}>
{n}
</li>
</a>
))}
</ul>
...
It logs undefined.
Also tried this: console.log(e.target.getAttribute('data-value')) and it returns null.
How do I get the value from a tag?
Stack: TypeScript: 3.8.3, React: 16.13.1
In frameworks like React and Vue you generally stay away from reading data from the DOM when possible. In this case, you can capture the value in a function:
const chosen = (e: any, value: any) => console.log(value)
...
<ul>
{numbers.map(n => (
<a key={n} onClick={(event) => { chosen(event, n); }}>
<li>
{n}
</li>
</a>
))}
</ul>
...
You can use the following code to do that:
export default function App() {
function chosen(event) {
const meta = event.target.parentNode.getAttribute("data-value");
console.log(meta);
}
return (
<ul>
{numbers.map((n) => (
<a data-value={n} onClick={chosen}>
<li key={n}>{n}</li>
</a>
))}
</ul>
);
}
li element should contentin the a element. Please try this example
import React from "react";
const numbers = [1, 2, 3, 4, 5];
function App() {
function chosen(event) {
event.preventDefault();
console.log(event.target.dataset.value);
}
return (
<ul>
{numbers.map((number) => {
return (
<li key={number}>
<a href="!#" onClick={chosen} data-value={number}>
{number}
</a>
</li>
);
})}
</ul>
);
}
export default App;
And follow the Ross Allen advice

How can I map through an object in ReactJS?

I have a response like this:
I want to display the name of each object inside this HTML:
{subjects.map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">{ item.name }</span>
</li>
))}
But it throws an error of subjects.map is not a function.
First, I have to define the keys of the objects where it creates an array of keys, where I want to loop through and show the subject.names.
What I also tried is this:
{Object.keys(subjects).map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subjects[i]}</span>
</li>
))}
When calling Object.keys it returns a array of the object's keys.
Object.keys({ test: '', test2: ''}) // ['test', 'test2']
When you call Array#map the function you pass will give you 2 arguments;
the item in the array,
the index of the item.
When you want to get the data, you need to use item (or in the example below keyName) instead of i
{Object.keys(subjects).map((keyName, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subjects[keyName]}</span>
</li>
))}
You get this error because your variable subjects is an Object not Array, you can use map() only for Array.
In case of mapping object you can do this:
{
Object.keys(subjects).map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">{ subjects[item].name }</span>
</li>
))
}
Use Object.entries() function.
Object.entries(object) return:
[
[key, value],
[key, value],
...
]
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
{Object.entries(subjects).map(([key, subject], i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subject.name}</span>
</li>
))}
Map over the keys of the object using Object.keys():
{Object.keys(yourObject).map(function(key) {
return <div>Key: {key}, Value: {yourObject[key]}</div>;
})}
I use the below Object.entries to easily output the key and the value:
{Object.entries(someObject).map(([key, val], i) => (
<p key={i}>
{key}: {val}
</p>
))}
Do you get an error when you try to map through the object keys, or does it throw something else.
Also note when you want to map through the keys you make sure to refer to the object keys correctly. Just like this:
{ Object.keys(subjects).map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subjects[item]}</span>
</li>
))}
You need to use {subjects[item]} instead of {subjects[i]} because it refers to the keys of the object. If you look for subjects[i] you will get undefined.
I am not sure why Aleksey Potapov marked the answer for deletion but it did solve my problem.
Using Object.keys(subjects).map gave me an array of strings containing the name of each object, while Object.entries(subjects).map gave me an array with all data inside witch it's what I wanted being able to do this:
const dataInfected = Object.entries(dataDay).map((day, i) => {
console.log(day[1].confirmed);
});
I hope it helps the owner of the post or someone else passing by.
Also you can use Lodash to direct convert object to array:
_.toArray({0:{a:4},1:{a:6},2:{a:5}})
[{a:4},{a:6},{a:5}]
In your case:
_.toArray(subjects).map((subject, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">Name: {subject[name]}</span>
</li>
))}

Categories

Resources