I am trying to delete a certain entry in my list according to its index. However, the delete button is in a separate component. So I need to pass the function that deletes the entry in the list from my current app component to the child content component.
The App.js is:
import { useState } from 'react';
import './App.css';
import Content from './components/Content/Content';
function App() {
const Tours = [
{
image: "",
title: "",
price: "$",
text: ""
}
]
const [list, setList] = useState(Tours);
function handleRemove(id) {
console.log(id)
const newList = list.filter((item) => item.id !== id);
setList(newList);
}
return (
<div>
{list.map((value, index) => {
return (
<div key={index}>
<Content tour={value} delete={ () => handleRemove(index)}/>
</div>)
})
}
</div>
);
}
export default App;
component.js is:
const Content = (props) => {
return (
<div className="single-tour">
<img src = {props.tour.image} alt= {props.tour.title}></img>
<div className="container">
<h4 className ="title">{props.tour.title}</h4>
<h4 className="tour-price">{props.tour.price}</h4>
<button className="delete-btn" onClick={()=> props.delete}>Delete</button>
</div>
</div>
);
};
export default Content;
the console.log in the handleDelete is not showing, meaning the function is not being passed. What is the problem?
Please try:
onClick={()=> props.delete()}
or
onClick={props.delete}
In order to remove the card according to the id values, please also add them to your Tours object:
const Tours = [
{
image: "",
title: "",
price: "$",
text: "",
id: 1,
}
]
You should pass the value to the function like this.
here is the codesandbox link
https://codesandbox.io/s/react-func-props-j6idr?file=/src/index.js
you should and id field to the the list of tours and pass it down to the component
function App() {
const Tours = [
{
id: 0,
image: "https://via.placeholder.com/150/92c952",
title: "title 1",
price: "$ 1",
text: "text"
},
{
id: 1,
image: "https://via.placeholder.com/150/d32776",
title: "title 2",
price: "$ 2",
text: "description"
}
];
const [list, setList] = useState(Tours);
function handleRemove(id) {
console.log(id);
const newList = list.filter((item) => item.id !== id);
setList(newList);
}
return (
<div>
{list.map((value, index) => {
return (
<div key={value.id}>
<Content tour={value} delete={() => handleRemove(index)} />
</div>
);
})}
</div>
);
}
in the content component, you should pass the id of the tour object to the function
const Content = (props) => {
return (
<div className="single-tour">
<img src={props.tour.image} alt={props.tour.title}></img>
<div className="container">
<h4 className="title">{props.tour.title}</h4>
<h4 className="tour-price">{props.tour.price}</h4>
<button
className="delete-btn"
onClick={() => props.delete(props.tour.id)}
>
Delete
</button>
</div>
</div>
);
};
You're filtering the array by id which the objects do not have. What you can do is pass an index the filter method:
const newList = list.filter((item, i) => i !== index);
However, a better practice would be to add an id attribute to the object instead of relying on order.
Related
I map buttons and need them to display text once clicked. I use an ID state to grab which key was clicked and then the classname should display that text, and should only display one description at a time.
Here's my code for the component:
import { useState } from 'react';
const items = [
{
name: 'Pizza',
description: 'Cheese, bread, red sauce',
price: '4.99',
key: 0,
},
{
name: 'Spaghetti',
description: 'cooked noodles, red sauce, parmesan',
price: '3.99',
key: 1,
},
{ name: 'Soda', description: 'pepsi products', price: '4.99', key: 2 },
];
const MenuItem = () => {
const [ID, setID] = useState(null);
const itemHandler = (item) => {
console.log(item);
if (ID === null) {
setID(item.key);
} else {
setID(null);
}
console.log(ID);
};
return (
<li className="items">
{items.map((item) => {
return (
<ul key={item.key}>
<button className="menu-item" onClick={itemHandler}>
{`${item.name} ${item.price}`}
</button>
<h4
className={`menu-description ${
ID === item.key ? 'active-item' : 'none'
}`}
>
{item.description}
</h4>
</ul>
);
})}
</li>
);
};
export default MenuItem;
When I console.log(item) I get the event (clicking the button) instead of item from the items array. Not sure what I can do to fix.
I'm learning React, JS and I'm trying to use the .map method to display some data in an array. For each element in the array I create a button to show/hide a description with css and I'm looking for a way to only show the description of one element instead of all descriptions. It is probably unclear but here is the code :
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const [showDescription, setshowDescription] = useState(false);
const [anArray] = useState([
{ title: "First Div", description: "First description"},
{ title: "Antoher Div", description: "Another description"}
]);
return (
<div className="App">
{anArray.map((val, index) => {
return (
<div className="div" key={index}>
<div className="title">{val.title}</div>
<button className="btn" onClick={() => setshowDescription(!showDescription)}>
Show description
</button>
<div id={showDescription ? "display" : "hidden"}>
<div className="description">{val.description}</div>
</div>
</div>
);
})}
</div>
);
}
Do you have any idea please ?
Issue
You've a single boolean showDescription state that is toggling every description.
Solution
Store an index of the description you want to toggle. Use the index to match the currently mapped element. Conditionally render the description
export default function App() {
const [showId, setShowId] = useState(null);
// curried function to toggle to new index or back to null to hide
const toggleDescription = id => () => setShowId(showId => showId === id ? null : id);
const [anArray] = useState([
{ title: "First Div", description: "First description"},
{ title: "Antoher Div", description: "Another description"}
]);
return (
<div className="App">
{anArray.map((val, index) => {
return (
<div className="div" key={index}>
<div className="title">{val.title}</div>
<button className="btn" onClick={toggleDescription(index)}> // <-- pass index
Show description
</button>
{showId === index && ( // <-- check for index match
<div>
<div className="description">{val.description}</div>
</div>
)}
</div>
);
})}
</div>
);
}
You can use index to show/hide your div. The issue is you are using only one Boolean value to handle it.
export default function App() {
const [showDescriptionIndex, setshowDescriptionIndex] = useState(-1);
const [anArray] = useState([
{ title: "First Div", description: "First description"},
{ title: "Antoher Div", description: "Another description"}
]);
return (
<div className="App">
{anArray.map((val, index) => {
return (
<div className="div" key={index}>
<div className="title">{val.title}</div>
<button className="btn" onClick={() => setshowDescriptionIndex(index)}>
Show description
</button>
<div id={showDescriptionIndex === index ? "display" : "hidden"}>
<div className="description">{val.description}</div>
</div>
</div>
);
})}
</div>
);
}
Try It
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const [showDescription, setshowDescription] = useState({});
const [anArray] = useState([
{ title: "First Div", description: "First description"},
{ title: "Antoher Div", description: "Another description"}
]);
return (
<div className="App">
{anArray.map((val, index) => {
return (
<div className="div" key={index}>
<div className="title">{val.title}</div>
<button className="btn" onClick={() => setshowDescription({...showDescription, [index]: !showDescription[index]})}>
Show description
</button>
<div id={showDescription && showDescription[index] ? "display" : "hidden"}>
{showDescription[index]}
<div className="description">{val.description}</div>
</div>
</div>
);
})}
</div>
);
}
Tip: Use class="show/hide" instead of id="show/hide"
i wrote code to expand "more info" block after clicking button, but function invoking only for first item.
Is it happening beacuse i use let more = document.getElementById("more"); ?
How can i change code for expanding only specifed item?
const Currency = ({ filteredItems, isLoading }) => {
const addListeners = () => {
let more = document.querySelectorAll(".more-info");
more.forEach(item => {
item.addEventListener("click", toggle)
})
console.log(more)
}
const toggle = () => {
let more = document.getElementById("more");
if (more.className === "more-info") {
more.className = "more-info-active";
} else {
more.className = "more-info";
}
}
return isLoading ? (<div className="loader">Loading...</div>) : (
<div items={filteredItems}>
{filteredItems.map((item) => (
<div key={item.id} className="item-wrapper">
<div className="item">
<h2>{item.name}</h2>
<img src={item.image} alt="crypto symbol"></img>
<h3>{item.symbol}</h3>
<p>{item.current_price} pln</p>
<button onLoad={addListeners} onClick={toggle} className="info-btn" id="item-btn" >➜</button>
</div>
<div id="more" className="more-info">
<div className="more-data">
<div className="info-text">
<p>high_24: {item.high_24h}</p>
<p>low_24: {item.low_24h}</p>
</div>
<div>
<p>price_change_24h: {item.price_change_24h}</p>
<p>price_change_percentage_24h: {item.price_change_percentage_24h}</p>
</div>
<div>
<Sparklines className="sparkline" height={60} margin={10} data={item.sparkline_in_7d.price}>
<SparklinesLine style={{fill:"none"}} color="#b777ff" />
</Sparklines>
</div>
</div>
</div>
</div>
))}
</div>
);
}
Dont use document.getElement... , this is a Real DOM but React uses Virtual DOM.
Instead create a state with an array and on onClick event pass item as an argument and store in state , you can store only id e.g.
Last step, check in JSX if state includes item.id , if true then expand
this is an example , keep in mind this is not the only solution. Just simple example.
import React, { useState } from "react";
const fakeData = [
{
id: "123123-dfsdfsd",
name: 'Title One',
description: "Description bla bla bla One"
},
{
id: "343434-dfsdfsd",
name: 'Title Two',
description: "Description bla bla bla Two"
},
{
id: "6767676-dfsdfsd",
name: 'Title Three',
description: "Description bla bla bla Three"
}
]
function App() {
const [tabs, setTabs] = useState([]);
function _onToggle(item) {
const isExist = tabs.includes(item.id)
if (isExist) {
setTabs(prevData => prevData.filter(pd => pd !== item.id))
} else {
setTabs(prevData => [item.id, ...prevData])
}
}
return (
<div className="app">
<div>
{
fakeData.map((item, i) => (
<div key={i}>
<h3 onClick={() => _onToggle(item)}>{item.name}</h3>
<p style={{ display: tabs.includes(item.id) ? 'block' : 'none' }}>
{ item.description }
</p>
</div>
))
}
</div>
</div>
);
}
export default App;
Hello Stackoverflow community!
I'am practicing with react. I am building a very simple shopping cart system. With the app you can select from products. It adds to the shoppingcart. I'm got this error message: TypeError: Cannot read property 'details' of null.
I'am attaching my code.
App.js
import React, {useState, useEffect } from "react";
import Shop from "./Shop";
import Cart from "./Cart"
const ShoppingItems = [{
id: 1,
details: {
type: "cloth",
name: "Blue jacket",
price: 15000
}
},
{
id: 2,
details: {
type: "cloth",
name: "Trousers",
price: 9990
}
},
{
id: 3,
details: {
type: "cloth",
name: "T-shirt",
price: 5000
}
}
];
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {console.log(selectedItem)}, [selectedItem]);
return(
<div className="ui container">
<Shop Shopitems={ShoppingItems} setSelectedItem={setSelectedItem}/>
<Cart selectedItem={selectedItem}/>
</div>
);
};
export default App;
Shop.js
import React from "react";
const Shop = ({Shopitems, setSelectedItem}) => {
const AddItem = (id) => {
const selectedItem = Shopitems.find( item => item.id === id);
if(selectedItem)
{
setSelectedItem(selectedItem);
}
return;
};
const renderedItems = Shopitems.map((shopitem) => {
return(
<div key={shopitem.id} className="card">
<div className="content">
<div className="header">{shopitem.details.name}</div>
<div className="description">
{shopitem.details.price + " Ft"}
</div>
</div>
<div onClick={() => AddItem(shopitem.id)} className="ui bottom attached button">
<i className="cart icon"></i>
Add to cart
</div>
</div>
);
});
return (
<div className="ui cards">{renderedItems}</div>
);
};
export default Shop;
Cart.js
import React, {useState, useEffect} from "react";
const Cart = ({selectedItem}) => {
const [shoppingCart, setShoppingCart] = useState([]);
useEffect(() => {
//Adding to the shopping cart when an element selected
setShoppingCart([...shoppingCart, selectedItem]);
}, [selectedItem]);
const renderedItems = shoppingCart.map((item) => {
return(
<ul>
<li key={item.id}>
<div className="item">
{item.details.name}
</div>
</li>
</ul>
);
});
return(
<div>
<h1>Shopping Cart</h1>
{renderedItems}
</div>
);
};
export default Cart;
You need to verify that selectItem is not null because you cannot use .map on null, it needs to be an array ([]).
change you code to
import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';
import Records from './Records';
import Shop from "./Shop";
import Cart from "./Cart"
const ShoppingItems = [{
id: 1,
details: {
type: "cloth",
name: "Blue jacket",
price: 15000
}
},
{
id: 2,
details: {
type: "cloth",
name: "Trousers",
price: 9990
}
},
{
id: 3,
details: {
type: "cloth",
name: "T-shirt",
price: 5000
}
}
];
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {console.log(selectedItem)}, [selectedItem]);
return(
<div className="ui container">
{selectedItem !== null ? <><Shop Shopitems={ShoppingItems} setSelectedItem={setSelectedItem}/> <Cart selectedItem={selectedItem}/> </>: <></>}
</div>
);
};
export default App;
This wil render without error but it is blanc page because you pass 'null' as value.
It may not be a value for the first time. To solve this problem, you can bet where you have this error:for example:
const renderedItems = Shopitems?Shopitems.map((shopitem) => {
return(
<div key={shopitem.id} className="card">
<div className="content">
<div className="header">{shopitem.details.name}</div>
<div className="description">
{shopitem.details.price + " Ft"}
</div>
</div>
<div onClick={() => AddItem(shopitem.id)} className="ui bottom attached button">
<i className="cart icon"></i>
Add to cart
</div>
</div>
);
}) :[] ;
return (
<div className="ui cards">{renderedItems}</div>
);
};
export default Shop;
'
Friends, how to show the logo for the company from "arr1", the link to which is located in "arr2"? That is, to make the logo consistent with the company
import React from "react";
import "./styles.css";
export default function App() {
const arr1 = [
{
id: "random1",
companyName: "Apple"
},
{
id: "random2",
companyName: "Samsung"
}
];
const arr2 = [
{
id: "random1",
companyName: "Apple",
logoUrl: "img.com/url"
},
{
id: "random2",
companyName: "Samsung",
logoUrl: "img.com/url"
}
];
const blockCreate = () => {
return arr1.map(item => {
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src="#" />
</span>
</p>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
<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>
arr1 - Apple === arr2 - AppleLogo (logoUrl)
Now trying to do like this
https://codesandbox.io/s/kind-bush-zlyi6
companyDetails is your array so, do like this arr2.companyDetails.find()
Change this
const blockCreate = (arr1, arr2) => {
return arr1.company.map(item => {
const src = arr2.companyDetails.find(a => a.id === item.id).logoUrl;
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src={src} alt="pic"/>
</span>
</p>
);
});
};
Something like this:
import React from "react";
import "./styles.css";
export default function App() {
const arr1 = {
company: [
{
id: "random1",
companyName: "Apple"
},
{
id: "random2",
companyName: "Samsung"
}
]
};
const arr2 = {
companyDetails: [
{
id: "random1",
companyName: "Apple",
logoUrl: "img.com/url"
},
{
id: "random2",
companyName: "Samsung",
logoUrl: "img.com/url"
}
]
};
const blockCreate = (arr1, arr2) => {
return arr1.company.map(item => {
const src = arr2.find(a => a.id === item.id).logoUrl;
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src={src} />
</span>
</p>
);
});
};
return (
<div className="App">
<div>{blockCreate(arr1, arr2)}</div>
</div>
);
}
Note that I have passed arr1 and arr2 into the blockCreate function. This is better form than just having it access the variables on the higher scope since it makes your blockCreate function more modular, hence reusable.
As a react developer you should think about how this component can be re-usable meaning you can pass a single array of object and then have the component to always render the list of details.
The right way of doing this is to prepare the data before passing it to this component (can be handled in the backend or front-end depends on how you are getting this data). This is not the component's job to map through two different arrays and figure out the relation between those and render the list.
Here is an example in ECMAScript6:
// Prepare the final data
const hash = new Map();
arr1.company.concat(arr2.companyDetails).forEach(obj => {
hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj));
});
const finalArray = Array.from(hash.values());
const blockCreate = (finalArray) => {
return arr3.map(item => {
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src={item.logoUrl} alt="logo" />
</span>
</p>
);
});
};