Need help for loop in React - reactjs

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"

Related

REACT.JS how to detect only one of the cards is clicked from a component

I tried to add click handler on my code, the idea is when i click on my first card, the first card add new class "selected" nad when i click on my third card or else the third card or else will add new class "selected". There is a problem when i was clicking on any card it was always first card was selected. Please help me. Thank you.
Parent code
import React, { useState } from 'react';
import CardBus from '../CardBus/CardBus.component';
import './BusSelector.style.css'
function BusSelector() {
const [buses, setBuses] = useState([
{
busNumber: 1,
destination: 'Cibiru - Cicaheum',
stopTime: '09:20 - 09.45',
stopLocation: 'Jl.Jendral Sudirman',
isSelected: false
},
{
busNumber: 2,
destination: 'Cicaheum - Cibereum',
stopTime: '09:10 - 09.20',
stopLocation: 'Jl.Soekarno Hatta',
isSelected: false
},
]);
return (
<div className="bus-selector--container">
{buses.map((bus) => {
return <CardBus key={bus.busNumber} eachBus={bus} buses={buses} setBuses={setBuses} />
})}
</div>
);
}
export default BusSelector;
Child code:
import React from 'react';
import './CardBus.style.css';
import TimeProgressThin from '../../icon/Time_progress_thin.svg';
import PinLight from '../../icon/Pin_light_thin.svg';
function CardBus(props) {
const [isSelected, setIsSelected] = useState(false)
let { eachBus, buses, setBuses} = props;
const selectedHandler = () => {
if (isSelected) {
const card = document.querySelector('.card');
card.classList.add('selected');
return setIsSelected(!isSelected);
}
else {
const card = document.querySelector('.card');
card.classList.remove('selected');
return setIsSelected(!isSelected);
}
}
return (
<div key={eachBus.key} className="card" onClick={selectedHandler}>
<div className="bus--left">
<h1>{eachBus.busNumber}</h1>
</div>
<div className="bus--right">
<div className="title">
<h1>{`Armada ${eachBus.busNumber}`}</h1>
<h2>{eachBus.destination}</h2>
</div>
<div className="detail">
<div className="detail--item">
<div>
<img src={TimeProgressThin} alt="Time Progress Logo" />
</div>
<div className="detail_content">
<h3>Last stopped</h3>
<h3>{eachBus.stopTime}</h3>
</div>
</div>
<div className="detail--item">
<div>
<img src={PinLight} alt="Pin Light Logo" />
</div>
<div className="detail_content">
<h3>Location Stopped</h3>
<h3>{eachBus.stopLocation}</h3>
</div>
</div>
</div>
</div>
</div>
);
}
export default CardBus;
Allow multiple selections
function CardBus(props) {
const [isSelected, setIsSelected] = useState(false);
let { eachBus, buses, setBuses } = props;
return (
<div key={eachBus.key} className={`card ${isSelected ? 'selected' : ''}`} onClick={() => setIsSelected(!isSelected)}>
...
</div>
);
}
export default CardBus;
Allow single select
You can simplify the code a lot if you move the selected child logic to the parent.
Parent code:
function BusSelector() {
const [buses, setBuses] = useState([
{
busNumber: 1,
destination: 'Cibiru - Cicaheum',
stopTime: '09:20 - 09.45',
stopLocation: 'Jl.Jendral Sudirman',
isSelected: false
},
{
busNumber: 2,
destination: 'Cicaheum - Cibereum',
stopTime: '09:10 - 09.20',
stopLocation: 'Jl.Soekarno Hatta',
isSelected: false
},
]);
const [selectedBus, setSelectedBus] = useState(-1);
return (
<div className="bus-selector--container">
{buses.map((bus) => {
return <CardBus
key={bus.busNumber}
eachBus={bus}
buses={buses}
setBuses={setBuses}
onClick={() => setSelectedBus(bus.busNumber)}
isSelected={bus.busNumber === selectedBus} />;
})}
</div>
);
}
export default BusSelector;
Child code:
function CardBus(props) {
let { eachBus, isSelected, buses, setBuses, onClick } = props;
return (
<div key={eachBus.key} className={`card ${isSelected ? 'selected' : ''}`} onClick={onClick}>
...
</div>
);
}
export default CardBus;

React, passing a function in props is not working

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.

Function invoking only for first item React

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;

Button Click to Render Div

I want to click the button and render its corresponding div. Should I add the the div's info that I want to render to the state?
I'm sure there's a few different ways to solve this but I want to do it the React way and maybe as a function component?
Updated
export default function About(props) (
const [isHidden, setIsHidden] = useState(true);
const handleClick = () => {
setIsHidden(!isHidden);
};
return (
<div className="row justify-content-md-center">
<div className="col-auto">
<CustomButton onClick={handleClick}>Website Develpment</CustomButton>
</div>
<div className="col-auto">
<CustomButton onClick={handleClick}>Wordpress Develpment</CustomButton>
</div>
<div className="col-auto">
<CustomButton onClick={handleClick}>Ecommerce Development</CustomButton>
</div>
</div>
<div className="row">
<div className="col-md-4">column 1</div>
<div className="col-md-4">column 2</div>
<div className="col-md-4">column 3</div>
</div>
);
)
You can store an ID as a string state variable, and set that variable on button press.
Then use ConditionalRendering to only display the div with the matching ID.
const {useState} = React;
function About(props) {
const [visibleItem, setVisibleItem] = useState('');
return (
<div>
<button onClick={() => setVisibleItem("website")}>
Show Website Develpment
</button>
<button onClick={() => setVisibleItem("wordpress")}>
Show Wordpress Develpment
</button>
<button onClick={() => setVisibleItem("ecommerce")}>
Show Ecommerce Development
</button>
{visibleItem === "website" &&
<div>
<h2>Wordpress Development</h2>
<p>Info about Wordpress and all the things I can do with it</p>
</div>
}
{visibleItem === "wordpress" &&
<div>
<h2>Ecommerce Development</h2>
<p>I can do eccomerce things too</p>
</div>
}
{visibleItem === "ecommerce" &&
<div>
<h2>Website Development</h2>
<p>Info about Webdev</p>
</div>
}
</div>
);
}
ReactDOM.render(
<About/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
If the sections were much bigger then I'd recommend splitting them out into separate components, or maybe doing an if or switch statement to select between them before the return, but the above snippet is a pattern I have used often with react.
You can create two states one holding array of button info to minimize repetition of code and another state to track the column clicked.
import React, { useState } from "react";
export default function About(props) {
const [colNum, setColNum] = useState('');
const [btnNames] = useState([
{
colId: 1,
name: "Website Develpment"
},
{
colId: 2,
name: "Wordpress Develpment"
},
{
colId: 3,
name: "Ecommerce Develpment"
}
]);
const handleClick = (id) => {
setColNum(id);
};
return (
<>
<div className="row justify-content-md-center">
{btnNames.map(element => (
<div className="col-auto" key={element.colId}>
<CustomButton onClick={()=> handleClick(element.colId)}>{element.name}</CustomButton>
</div>
))}
</div>
{colNum !== '' ? (<div className="row">
<div className="col-md-4">column {colNum}</div>
</div>) : null }
</>
);
}
So you can define showing value for each section in state, which initial should be false. Then we can handle this state with a function where built-in JS tools can be used (Object.entries, Object.fromEntries and map). The element will be displayed, the value of which will be true, and the rest will automatically become false. More details can be found here: https://codesandbox.io/s/happy-sea-nckmw?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
import styles from "./TestStyles.module.css";
import CustomButton from "./CustomButton";
export default function App() {
const [showDiv, setShowDiv] = useState({
web: false,
wordpress: false,
ecommerce: false
});
const showDivHandler = (val) => {
const newState = Object.fromEntries(
Object.entries(showDiv).map(([key, value]) => [key, (value = false)])
);
setShowDiv({
...newState,
[val]: !showDiv[val]
});
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div className={styles.Section}>
<div className="col-auto">
<CustomButton clicked={() => showDivHandler("web")}>
Website Develpment
</CustomButton>
</div>
<div className="col-auto">
<CustomButton clicked={() => showDivHandler("wordpress")}>
Wordpress Develpment
</CustomButton>
</div>
<div className="col-auto">
<CustomButton clicked={() => showDivHandler("ecommerce")}>
Ecommerce Development
</CustomButton>
</div>
</div>
<div className={styles.Section} style={{marginTop: 20}}>
{showDiv.web && <div className={styles.Content}>column 1</div>}
{showDiv.wordpress && <div className={styles.Content}>column 2</div>}
{showDiv.ecommerce && <div className={styles.Content}>column 3</div>}
</div>
</div>
);
}
The custom button can look like this:
import React from "react";
const button = (props) => (
<button
onClick={props.clicked}
//some other props if need it(e.g. styles)
>
{props.children}
</button>
);
export default button;
This is how I solved it. Thanks for your help!
export default function About() {
const [visibleItem, setVisibleItem] = useState(1);
const [priceItems] = useState([
{
id: 1,
name: "website",
},
{
id: 2,
name: "wordpress",
},
{
id: 3,
name: "ecommerce",
},
]);
const handleClick = (id) => {
setVisibleItem(id);
};
return (
<div className="container-fluid pricing-wrapper">
<div className="row">
<div className="col">
<h1>Pricing</h1>
</div>
</div>
<div className="row">
<div className="col">
<p>Innovation that empowers your team while also reducing risk!</p>
<div className="seperator"></div>
</div>
</div>
<div className="row justify-content-md-center">
<div className="row justify-content-md-center">
{priceItems.map((item) => (
<div className="col-auto">
<CustomButton onClick={() => setVisibleItem(item.id)}>
{item.name}
</CustomButton>
</div>
))}
</div>
</div>
{priceItems
.filter((item) => item.id === visibleItem)
.map((item) => (
<PriceItem item={item} />
))}
</div>
);
}

style react component on click

so i have this simple divs of names:
i just want to press on one of them and get a background color of green and when pressing on another one the first one will be canceled so just one will be colored at a time. what i simply need is inline style or i don't know i'm stuck.
first.js:
import React from 'react';
function SidebarComponents({name,title,selected,onSelect}) {
const style={
cursor: "pointer"
};
const classes = {
selected: {
backgroundColor: '#00ff00'
}
}
return (
<div
name={name}
title = {title}
style={style}
>
{name}
</div>
)
}
export default SidebarComponents;
second.js:
import React, { useEffect, useState } from "react";
import SidebarComponents from "../SidebarComponents/SidebarComponents";
import 'bootstrap/dist/css/bootstrap.min.css';
import '../Sidebar1/Sidebar.css';
function Sidebar({ onChange }) {
const [selectedComponent, setSelectedComponent] = useState({
componentsName: [
{ name: "John Smith", title: "John Smith" },
{ name: "Male, 26 years old", title: "Gender and age" },
{ name: "john", title: "Alerts" },
{ name: "claude", title: "Recent" },
{ name: "edward", title: "Blood pressure" },
{ name: "mira", title: "Body weight" },
{ name: "alex", title: "Glucose" },
{ name: "zac", title: "SPO2" }
]
});
return (
<div>
{selectedComponent.componentsName.map(component => {
return (
<div className="row align-items-start sidebar-components">
<div className="col">
<SidebarComponents
name={component.name}
title={component.title}
/>
</div>
</div>
);
})}
</div>
);
}
export default Sidebar;
on Sidebar:
const [selectedName, setSelectedName] = useState(null);
//...
<SidebarComponents
name={component.name}
title={component.title}
selected={component.name === selectedName}
onSelect={setSelectedName}
/>
on SidebarComponents:
const selectedClassName = selected ? 'selected' : '';
//...
<div
name={name}
title={title}
style={style}
className={`sidebar ${selectedClassName}`} //you must add sidebar and selected classes to your styles
onClick={() => onSelect(name)}
>
{name}
</div>
Add key attribute to div, inside the map.
Handel onClick event, to store the selected element index/value in your state.
Apply style using conditional rendering of className.
second.js
<div>
{selectedComponent.componentsName.map((component, index) => {
return (
<div key={index} onClick={() => handelOnClick(index)} className="row align-items-start sidebar-components">
<div className="col">
<SidebarComponents
name={component.name}
title={component.title}
className={selectedIndex === index ? 'highlight' : ''}
/>
</div>
</div>
);
})}
</div>
As you are rendering text in first.js no need to use div wrapper, use 'p', 'span' tag
In second.js instead of iterating entire div block, use ul li

Resources