How to get data-attribute in React? - javascript

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

Related

React how to call function depends on specific element id

how I can tigress an function depends on id of an element. Right now all elements getting clicked if I click on any single element. how to prevent to show all element ? here is my code
const[showsubcat,setShowSubCat] = useState(false)
let subcategory=(()=>{
setShowSubCat(prev=>!prev)
})
my jsx
{data.map((data)=>{
return(
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >{data.main_category}</li>
{showsubcat &&
<li><i class="las la-angle-right" id="sub_category"></i> {data.sub_category}</li>
}
</>
)
see the screenshot. I am clicking on single items but it's showing all items.
Every li should have it own state
so it's either you create states based on number of li if they're just 2 elements max! but it's ugly and when you want to add more li it's gonna be a mess
so you just create a component defining the ListItem and every component has it own state.
function ListItem({data}) {
const[showsubcat,setShowSubCat] = useState(false)
const subcategory= ()=> setShowSubCat(prev=>!prev)
return (
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >
{data.main_category}
</li>
{showsubcat &&
<li>
<i class="las la-angle-right" id="sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
and you use it in the list component like this
data.map((datum, index) => <ListItem key={index} data={datum} />
EDIT AFTER THE POST UPDATE (misunderstanding)
the list item (or the block containing the li and the helper text) should be an independant component to manage it own state
function PostAds(data) => {
return (
<>
{
data.map((data, index) => <ListItem key={index} data {data}/>
}
</>
)
}
function ListItem({data}) {
const [showsubcat, setShowSubCat] = useState(false)
const subcategory = () => setShowSubCat(prev => !prev)
return (
<>
<li
class="list-group-item"
id={data.id}
onClick={subcategory}>
{data.main_category}
</li>
{
showsubcat &&
<li >
<i class = "las la-angle-right" id = "sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
The reason this is happening is because you are using the same variable showsubcat to check if the category was clicked or not.
A proper way to do this would be by either making showsubcat as an array that holds ids of those categories that were clicked like:
const[showsubcat,setShowSubCat] = useState([])
let subcategory=((categoryId)=>
showsubcat.includes(categoryId) ?
setShowSubCat(showsubcat.filter(el => el !== categoryId)) :
setShowSubCat([...showsubcat, categoryId]));
and then while mapping the data:
{data.map((category)=>
(
<>
<li class="list-group-item" id={category.id} key={category.id}
onClick={() => subcategory(category.id)}>
{category.main_category}
</li>
{showsubcat.includes(category.id) &&
<li>
<i class="las la-angle-right"
id="sub_category" key={`subCategory${category.id}`} />
{category.sub_category}
</li>
}
</>
)
}
The other method would be to add a new key in your data array as selectedCategory and change its value to true/false based on the click, but this is a bit lengthy, let me know if you still want to know that process.
Also, accept the answer if it helps!

Show All Button - JSX Logic

I'm trying to develop a button that once I click it I show all the items available on my shopping list. But I'm struggling to put all the pieces together and come up with the correct syntax.
I have an array with 20 items and I would like to have initially only 15 (I'm assuming I'd have to use useState there?) displayed on the screen. Once I'd click the button it would show all the items on my shopping list array. Can any body help me to structure this feature? Thanks :)
const showAll = useCallback(() => {
const availableItems = items;
if (availableItems > 15) {
//STRUGGLING
}
}, []);
return (
<div className="items.container">
<ul className="shoplist-items">
{items.map((item, i) => {
return (
<li className="items">
<div className="single-item" key={i}>
{item}
</div>
</li>
);
})}
</ul>
<div className="show-all-container">
<p onClick={showAll}>Show all</p>
</div>
</div>
);
Use useState hook from react, init your avaible items only with 15 elements to show, and map avaibleItems instead items, and onClick just set avaibleItems:
const [avaibleItems, setAvaibleItems] = useState(items.slice(0,15);
const showAll = () =>{ setAvaibleItems(items)}
const allItems = useRef(items);
const [visibleItems, setVisibleItems] = useState(allItems.current.slice(0,15));
const showAll = useCallback(() => {
if(allItems.current.length > 15) {
setVisibleItems(allItems.current);
}
}
return (
<div className="items.container">
<ul className="shoplist-items">
{visibleItems.map((item, i) => {
return (
<li className="items">
<div className="single-item" key={i}>
{item}
</div>
</li>
);
})}
</ul>
<div className="show-all-container">
<p onClick={showAll}>Show all</p>
</div>
</div>
);

I am trying to print array inside object using map function but I get :Cannot read property map of undefined

I'm trying to print the properties of Selectedproduct object inside Modal section and every thing works well until it reaches to "description" array property , it shows me "Cannot read property 'map' of undefined". eventhough when I use console.log(Selectedproduct) the description property appears normally,but when I code console.log(Selectedproduct.description) I dont know why it consider it as undefined .can you please tell me why it can't see the description as stand alone property ?
import React, { Component } from "react";
import FormatCurrency from "../Components/util";
import Slide from "react-reveal/Slide";
import Modal from "react-modal";
import Zoom from "react-reveal/Zoom";
import { connect } from "react-redux";
import { GetProducts } from "../Actions/ItemsActions";
import { AddToCart } from "../Actions/CartActions";
class Products extends Component {
constructor(props) {
super();
this.state = {
show: false,
Selectedproduct: {},
};
}
showModal = (product) => {
console.log(product);
this.setState({ show: true, Selectedproduct: product });
};
hideModal = () => {
this.setState({ show: false });
};
componentDidMount() {
this.props.GetProducts();
}
render() {
const { Selectedproduct } = this.state;
return (
<div>
<Slide left cascade={true}>
{!this.props.products ? (
<div> Loading..</div>
) : (
<ul className="products">
{this.props.products.map((product) => (
<li key={product._id}>
<div className="product">
<a href={"#" + product._id}>
<img
src={product.image}
alt={product.title}
onClick={() => this.showModal(product)}
></img>
<p>{product.title}</p>
</a>
<div className="product-price">
<div> {FormatCurrency(product.price)}</div>
<button
onClick={() => this.props.AddToCart(product)}
className="button primary overlay"
>
{" "}
Add to cart
</button>
</div>
</div>
</li>
))}
</ul>
)}
</Slide>
<Modal isOpen={this.state.show} onRequestClose={this.hideModal}>
<Zoom>
<button className="close-modal" onClick={this.hideModal}>
x
</button>
<div className="product-details">
<img
src={Selectedproduct.image}
alt={Selectedproduct.title}
></img>
<div className="product-details-description">
<p>{Selectedproduct.title}</p>
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
<div className="product-price">
<div>{FormatCurrency(Selectedproduct.price)}</div>
<button
className="button primary"
onClick={() => {
this.props.AddToCart(Selectedproduct);
this.hideModal();
}}
>
{" "}
Add to cart
</button>
</div>
</div>
</div>
</Zoom>
</Modal>
</div>
);
}
}
export default connect((state) => ({ products: state.products.filterdItems }), {
GetProducts,
AddToCart,
})(Products);
Try this as your state property seems still undefined at runtime.
{Selectedproduct.description.map((x)=>(<li>x</li>))}
replace with:
{Selectedproduct && Selectedproduct.description? Selectedproduct.description.map((x)=>(<li>x</li>)):null}
description is likely undefined. Instead of:
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
just put in this temporary code to try and see what your object really looks like:
<ul>
console.dir("### DESCRIPTION IS:", Selectedproduct.description)
</ul>
and the open your browser dev tools to see what this prints to the console.
UPDATE based on comment after using console.log:
If you are getting something like availableColors: Array(2) for Selectedproduct you cannot print an array out to your <li> tags. An array is not a string. You have to unnest the inner arrays first.
So if your structure is Selectedproduct.description.availableColors = ['blue', 'red'] just as an example, you will need code like:
const { availableColors, otherAttribute1, otherAttribute2 } = Selectedproduct.description // destructure all array attributes from description
...
and then later in the component, do:
<ul>
{ availableColors.map(_ => <li>_</li>)}
{ otherAttribute1.map(_ => <li>_</li>)}
{ otherAttribute2.map(_ => <li>_</li>)}
</ul>

Don't output same items from array

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>

Add a unique key to dynamic children without having any unique key

I'm mapping some arrays in a React project and I return li-tag children, of course React expects a unique key for every dynamic child. However, I don't think I have any unique key... At least, not that I know of. With my data and code (being fetched from https://tmi.twitch.tv/group/user/instak/chatters), is there any key i can pass?
import React, {Component} from 'react';
export default class Overview extends Component{
constructor(props, context){
super(props, context);
this.state = {
chatters: {
moderators: [],
staff: [],
admins: [],
global_mods: [],
viewers: []
}
};
}
componentWillMount() {
fetch('/api/overview') // fetch from Express.js server
.then(response => response.json())
.then(result => this.setState({
chatters: result.chatters
}));
}
render(){
let {chatters} = this.state;
return (
<div>
<h2>Chatters</h2>
<div>
<h3>Moderators</h3>
<ul>
{chatters.moderators.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Staff</h3>
<ul>
{chatters.staff.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Admins</h3>
<ul>
{chatters.admins.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Global Mods</h3>
<ul>
{chatters.global_mods.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Plebs</h3>
<ul>
{chatters.viewers.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
</div>
);
}
}
Just use the twitch username. React doesn't need some fancy key, it just needs to be a unique value that stays the same for that individual rendered element.
example:
chatters.viewers.map(chatterName => {
return <li key={chatterName}>{chatterName}</li>;
})

Categories

Resources