I am trying to map over specific values in an array of objects.
I have collected data from my Backend API and this is the response:
// An array of objects
console.log(response.data) =
[
{name:"Bryan",age:"25",sport:"basketball"},
{name:"Justin",age:"30",sport:"soccer"},
{name:"Mark",age:"28",sport:"basketball"},
{name:"Cowell",age:"27",sport:"soccer"},
]
I put this data into a state ("data") using "useState()" from React and used a useEffect to unpack the data upon rendering.
const [data, setData] = useState([])
// some function to store response.data in state
setData(response.data)
I want to map these values onto my Browser in the following way such that Soccer players are displayed in the first div and Basketball players in the second div:
(tried several ways but they resulted in parsing errors)
function App() {
const [data, showData] = useState([])
return (
<div>
{data.map(info => {
<div>
<h1> Sport: soccer </h1>
<h5> {info.name} </h5>
</div>
<div>
<h1> Sport: basketball</h1>
<h5> {info.name} </h5>
</div>
}
)}
</div>
)
}
I am trying to group the names within the same div block (same sport) and not 2 different div blocks for each sport.
You need to return the elements from the map function and also remove the part where you hardcode basketball.
{data.map((info, idx) => (
<div key={idx}>
<h1> Sport: {info.sport} </h1>
<h5> {info.name} </h5>
</div>
))}
const groupBy = (array, getGroupByKey) => {
return (
(array &&
array.reduce((grouped, obj) => {
const groupByKey = getGroupByKey(obj);
if (!grouped[groupByKey]) {
grouped[groupByKey] = [];
}
grouped[groupByKey].push(obj);
return grouped;
}, {})) ||
{}
);
};
const App = (props) => {
var [data, setData] = React.useState([
{ name: 'Bryan', age: '25', sport: 'basketball' },
{ name: 'Justin', age: '30', sport: 'soccer' },
{ name: 'Mark', age: '28', sport: 'basketball' },
{ name: 'Cowell', age: '27', sport: 'soccer' },
]);
const players = groupBy(data, (player) => player.sport);
const sportKeys = Object.keys(players);
return (
<div>
{sportKeys.map((info, idx) => (
<div key={idx}>
<h1> Sport: {info} </h1>
{players[info].map((player, i) => (
<h5 key={i}>{player.name}</h5>
))}
</div>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Related
I'm displaying different cars and a button to add or remove the selections the user has made. How do I get the buttons to change state individually? As of now, it changes the state of all the buttons to one value.
const cars = [
{ name: "Benz", selected: false },
{ name: "Jeep", selected: false },
{ name: "BMW", selected: false }
];
export default function App() {
const isJeepSelected = true;
const isBenzSelected = true;
return (
<div className="App">
{cars.map((values, index) => (
<div key={index}>
<Item
isBenzSelected={isBenzSelected}
isJeepSelected={isJeepSelected}
{...values}
/>
</div>
))}
</div>
);
}
const Item = ({ name, isBenzSelected, isJeepSelected }) => {
const [toggle, setToggle] = useState(false);
const handleChange = () => {
setToggle(!toggle);
};
if (isBenzSelected) {
cars.find((val) => val.name === "Benz").selected = true;
}
console.log("cars --> ", cars);
console.log("isBenzSelected ", isBenzSelected);
console.log("isJeepSelected ", isJeepSelected);
return (
<>
<span>{name}</span>
<span>
<button onClick={handleChange}>
{!toggle && !isBenzSelected ? "Add" : "Remove"}
</button>
</span>
</>
);
};
I created a working example using Code Sandbox. Could anyone please help?
There's too much hardcoding here. What if you had 300 cars? You'd have to write 300 boolean useState hook calls, and it still wouldn't be dynamic if you had an arbitrary API payload (the usual case).
Try to think about how to generalize your logic rather than hardcoding values like "Benz" and Jeep. Those concepts are too closely-tied to the arbitrary data contents.
cars seems like it should be state since you're mutating it from React.
Here's an alternate approach:
const App = () => {
const [cars, setCars] = React.useState([
{name: "Benz", selected: false},
{name: "Jeep", selected: false},
{name: "BMW", selected: false},
]);
const handleSelect = i => {
setCars(prevCars => prevCars.map((e, j) =>
({...e, selected: i === j ? !e.selected : e.selected})
));
};
return (
<div className="App">
{cars.map((e, i) => (
<div key={e.name}>
<Item {...e} handleSelect={() => handleSelect(i)} />
</div>
))}
</div>
);
};
const Item = ({name, selected, handleSelect}) => (
<React.Fragment>
<span>{name}</span>
<span>
<button onClick={handleSelect}>
{selected ? "Remove" : "Add"}
</button>
</span>
</React.Fragment>
);
ReactDOM.createRoot(document.querySelector("#app"))
.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Consider generating unique ids for elements rather than using indices or assuming the name is unique. crypto.randomUUID() is a handy way to do this.
Why this does not remove item from the list but only for console.log? As you can see i update list in the function that i assign later to the button.
let DATA = [
{
id: 1,
name: "item1"
},
{
id: 2,
name: "item2"
}
];
const App = () => {
const deleteItem = (id) => {
DATA = DATA.filter((item) => item.id !== id);
console.log(DATA);
};
return (
<div className="App">
{DATA.map((item) => (
<p key={item.id} onClick={() => deleteItem(item.id)}>
{item.name}
</p>
))}
</div>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root")
).render(<App/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
It does remove the item from the list. But there's nothing telling the component to re-render the UI.
What you're looking for is state in React. Updating state triggers a component re-render:
const App = () => {
// store the data in state
const [data, setData] = React.useState([
{
id: 1,
name: "item1"
},
{
id: 2,
name: "item2"
}
]);
const deleteItem = (id) => {
// update state with the new data
setData(data.filter((item) => item.id !== id));
};
return (
<div className="App">
{data.map((item) => (
<p key={item.id} onClick={() => deleteItem(item.id)}>
{item.name}
</p>
))}
</div>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root")
).render(<App/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
It deletes it but react does not re-render. You have to use a setState method to trigger the re-render, you could put a copy of DATA in a state variable.
With the code below:
<!DOCTYPE html>
<html>
<body>
<head>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
</body>
<script type="text/babel">
const api = {
data: [
{
id: 1, party: 'Zuckerberg', news: [
{id: 1, headline: 'Zuckerberg news1'},
{id: 2, headline: 'Zuckerberg news2'},
]
},
{
id: 2, party: 'Musk', news: [
{id: 1, headline: 'Musk news1'},
{id: 2, headline: 'Musk news2'},
]
},
]
}
const App = () => {
const [data, setData] = React.useState([])
React.useEffect(() => setData(api.data), [])
const handleSelectChange = (e) => {
const name = e.target.value
setData(prev => prev.filter(datum => datum.party === name))
}
return (
<div>
<select
onChange={handleSelectChange}>
{data.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{data.map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
</html>
how can I prevent the reduction of dropdown options upon dropdown change, preferably without changing the api.data structure?
I basically need this dropdown to be refreshed only when api.data returns new data.
Is it possibly while keeping only single state (data)?
One way to handle this scenario by define state as an object like I mentioned in my comment. This might help you. It's working for me.
const MyComponent = () => {
const [data, setData] = React.useState({})
React.useEffect(() => setData({dropdownValues: api.data, filteredValues: api.data}), [])
const handleSelectChange = (e) => {
const name = e.target.value
const filteredValues = data.dropdownValues.filter(datum => datum.party === name);
setData({...data, filteredValues})
}
return (
<div>
<select
onChange={handleSelectChange}>
{data.dropdownValues && data.dropdownValues.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{data.filteredValues && data.filteredValues.map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
I can't think of a way to do it without using another state variable. What's the reason for only wanting one variable?
You could use an object state variable as Qubaish said but that can be much more difficult to maintain than just using a second state variable.
If you can get away with using two state variables use the following:
const App = () => {
const [data, setData] = React.useState([]);
const [options, setOptions] = useState([]);
React.useEffect(() => {
setData(api.data);
setOptions(api.data);
}, []);
const handleSelectChange = (e) => {
let name = e.target.value;
setData(options.filter((datum) => datum.party === name));
};
return (
<div>
<select onChange={handleSelectChange}>
{options.map((datum) => (
<option key={datum.id}>{datum.party}</option>
))}
</select>
{data.map((datum) =>
datum.news.map((newsItem) => (
<div key={newsItem.id}>{newsItem.headline}</div>
))
)}
</div>
);
};
It just stores a copy of the potential options in the options state and then stores the data to be displayed in "data".
I've modified your code below.
Main points:
removed useEffect - the API is static so doesn't need to be re-allocated
useState only monitors the filter (filtering of the API data only happens when the state changes so there isn't a need to store this - but you could useMemo if you wanted and make it dependent on the data)
The options are derived from api.data
<!DOCTYPE html>
<html>
<body>
<head>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
</body>
<script type="text/babel">
const api = {
data: [
{
id: 1, party: 'Zuckerberg', news: [
{id: 1, headline: 'Zuckerberg news1'},
{id: 2, headline: 'Zuckerberg news2'},
]
},
{
id: 2, party: 'Musk', news: [
{id: 1, headline: 'Musk news1'},
{id: 2, headline: 'Musk news2'},
]
},
]
}
const App = () => {
const [data, setData] = React.useState(api.data.length?api.data[0].party:undefined)
const handleSelectChange = (e) => {
setData(e.target.value)
}
return (
<div>
<select
onChange={handleSelectChange}>
{api.data.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{api.data.filter(datum=>datum.party===data).map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
</html>
I am working my way through the ReactSortable examples (react-sortablejs), but I cant figure out how to prevent adding duplicate objects when cloning from one list to another:
const [state, setState] = useState([
{ id: 0, name: "shrek", ...defs },
{ id: 1, name: "fiona", ...defs }
]);
const [state2, setState2] = useState([
]);
...
<div>
<ReactSortable
group={{ name: "cloning-group-name", pull: "clone" , put: false }}
animation={200}
delayOnTouchStart={true}
delay={2}
list={state}
setList={setState}
clone={item => ({ ...item })}
sort= {false}
>
{state.map(item => (
<div key={item.id}>{item.name}</div>
))}
</ReactSortable>
<ReactSortable
// here they are!
group={{ name: "cloning-group-name"}}
delayOnTouchStart={true}
delay={2}
list={state2}
setList={setState2}
onAdd = { }
>
{state2.map(item => (
<div key={item.id}>{item.name}</div>
))}
</ReactSortable>
I've been trying to use the onAdd, but I just can't figure out the logic. Thanks in advance.
I am currently learning React and I am trying to render my JSON data to the page. I used the map(), however it is only rendering the last object from the array onto the page.
class ContentCard extends Component {
state = {
products: ProductInfo
};
render() {
console.log(this.state.products);
return (
<>
{this.state.products.map(items => (
<div className="container page-wrapper">
<div className="page-inner">
<div className="row">
<div className="el-wrapper">
<div className="box-up">
<img className="img" src={items.img} alt="" />
<div className="img-info">
<div className="info-inner">
<span className="p-name">{items.name}</span>
<span className="p-company">{items.company}</span>
<span className="price">
{items.price.toLocaleString("en-US", {
style: "currency",
currency: "USD"
})}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
))}
</>
);
}
}
Here is my Json Data. The image links would have been too long so i just added a sample image to make it easier to read.
const products = [
{
id: 1000,
company: "YEEZY",
categoryName: "Men",
category: "tShirt",
name: "I feel like Pablo",
img: "http://code.slicecrowd.com/labs/4/images/t-shirt.png",
price: 120.0
},
{
id: 1000,
company: "H&M",
categoryName: "Men",
category: "tShirt",
name: "butterfly",
img: "http://code.slicecrowd.com/labs/4/images/t-shirt.png",
price: 120.0
},
{
id: 1000,
company: "North Face",
categoryName: "Men",
category: "Jacket",
name: "WindBreaker",
img: "http://code.slicecrowd.com/labs/4/images/t-shirt.png",
price: 120.0
}
];
export default products;
You need to add a key when you render multiple items in an array.
array.map(item => (
<div key={item.unique_property}>
[...]
</div>
))
Also, are you definitely on React 16.2 + ? I.e. do you have the necessary Fragment syntax support?
This example could help you:
import React, { useState, useEffect } from 'react';
const MyPost = () => {
const [post, setPost] = useState([]);
useEffect(() => {
fetch('url')
.then(res => res.json())
.then(jsonData => setPost([...jsonData]));
}, []);
return (
<>
{post.length > 0 ? (
<ol>
{post.map(p => (
<li key={p.replace(/' '/g, '')}>{p}</li>
))}
</ol>
) : (
<h1>No post Available</h1>
)}
</>
);
};
export default MyPost;