Toggle click on a mapped data React JS - reactjs

Here is my main component in which I render a component based on the data I receive from API:
<div className="PlayersContainer">
{players.map((player) => {
return (
<PlayerPhoto
key={player._id}
{...player}
selected={selected}
something={(e) => {
setSelected(!selected);
}}
/>
);
})}
</div>
and here is inside my component:
export default function PlayerPhoto({ name, img, something, selected }) {
return (
<article
className={selected ? styles.Fade : styles.PlayerBox}
onClick={something}
>
<img src={img} alt={name} className={styles.PlayerPhoto} />
<p className={styles.PlayerName}>{name}</p>
</article>
);
}
What I'm trying to do is that when the user clicks on a player it shoud take the fade class and become fade, and when the user clicked again it should returns to its normal class.
the problem is when the user clicks on a player all players get the fade class and become selected. How can I get their id and add the fade class to that specific player id?

Why are you not move this logic to PlayerPhoto?
export default function PlayerPhoto({ name, img }) {
const [selected, setSelected] = React.useState(false);
return (
<article
className={selected ? styles.Fade : styles.PlayerBox}
onClick={()=>setSelected((prevValue)=>!prevValue}
>
<img src={img} alt={name} className={styles.PlayerPhoto} />
<p className={styles.PlayerName}>{name}</p>
</article>
);
}

Create a state to maintain the selected id and then compare the selectedId and player id for the selected prop boolean value. if the id matches , it will change the className.
const [selectedId, setSelectedId] = useState(null);
<div className="PlayersContainer">
{players.map((player) => {
return (
<PlayerPhoto
key={player._id}
{...player}
selected={player._id === selectedId}
something={() => setSelected(player._id)}
/>
);
})}
</div>;

Related

Add dynamic link from an object in React

I'm new to React and I'm trying to insert a Link in a React Component. I made an object, and each item contains an external link. This the object :
export const myList =
[
{
"id":"P1",
"title":"Title1",
"description":"The first description",
"link":"https://random-link.io",
"cover":require("../img/myImg1.webp")
},
{
"id":"P2",
"title":"Title2",
"description":"The second description",
"link":"https://random-link2.io",
"cover":require("../img/my2ndImg.webp")
},
...
];
The main idea is to create pages for each item of the list, and a link to an external page to see more information.
I tried to do this :
function Page() {
const id = useParams();
const pageList = myList.find(list => list.id === id.id);
return(
<>
{
pageList ? (
<div className="Page">
<img className="ListCover" src={pageList?.cover} alt={pageList?.title}/>
<div className="information-list">
<span className="title-list">{pageList?.title}</span>
</div>
<div className="description-list">
<Dropdown titre="Description" description={pageList?.description} link={pageList?.link} />
</div>
</div>
) : <Navigate replace to="/404"/>
}
</>
)
}
export default Page;
In the Dropdown component, I made this :
function Dropdown({title, description, link}) {
const [open, setOpen] = useState(false);
return(
<div className="dropdown" id={`dropdown-${title}`}>
<div className="header-dropdown">
<div className="title-dropdown">{title}</div>
<span className={`arrow-dropdown ${open}`} onClick={() => setOpen(!open)}>
<img src={arrow} alt="Open it"/>
</span>
</div>
{
/* if dropdown is TRUE then it show the description */
open && <div className="description-dropdown">{description}
See more </div>
}
</div>
);
}
export default Dropdown;
The problem is that the link sends me to : http://localhost:3000/[object%20Object]; with another method I got http://localhost:3000/myProjet/https://random-link.io
I believe that the only issue in your code is that you are trying to use an object as the parameter for href which takes a string, try to just put link in there and it should work. it should look like:
<a href={link} rel='noreferrer'>

React.js close all the previous containers at the same time

I got the container with children coming from props.[Container][1]
[1]: https://i.stack.imgur.com/3Y7Qm.png . When i click the arrow button it shows the content of the container. [Content][1]
[1]: https://i.stack.imgur.com/A8eZH.png . When i open the content container i want other containers to close . For now i can only close them with clicking the arrow button again.[Open Content][1]
[1]: https://i.stack.imgur.com/REh57.png .Here is my code `
import { useState } from "react";
export default function Question(props) {
const [clicked, setClicked] = useState(false);
function clickedElement() {
return setClicked(!clicked);
}
return (
<div className="question-cont">
<div className="question-cont-inner">
<h3>{props.head}</h3>
<button onClick={() => clickedElement()}>
{clicked ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
</div>
{clicked ? <p>{props.description}</p> : ""}
</div>
);
}
Here is the my parent component
import Question from "../components/Question";
import questions from "../components/Questions";
export default function Sorular() {
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
/>
);
});
return (
<div className="sorular-container">
<div className="sorular-top">
<div className="sorular-top-back-img">
<a href="/">
<img
src="./images/right-arrow-colorful.png"
id="right-arrow-img"
/>
</a>
</div>
<div className="sorular-top-head">
<img src="./images/conversation.png" />
<h4>Sıkça Sorulan Sorular</h4>
</div>
</div>
<div className="sorular-bottom">{questionList}</div>
</div>
);
}
`
You need to remove your const [clicked, setClicked] = useState(false); state variable from the component itself and move it into parent:
In parent add this at the beggining and modify questionList:
const [clickedElementId, setClickedElementId] = useState(null);
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
isOpened={question.id === clickedElementId}
onClickedElement={() => setClickedElementId(
question.id === clickedElementId ? null : question.id
)}
/>
);
});
And in the Question.jsx, swap button for the following:
<button onClick={() => props.onClickedElement()}>
{props.isOpened ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
// and later:
{props.isOpened ? <p>{props.description}</p> : ""}
This works by your app holding id of only one, currently open question, and swap it based on clicked element.
Note that questionId should be unique amongst all Question components, but you probably use .map to render them so you should use the same variable as you are passing into Question's key prop while rendering.

Active object of the mapped array show center in scroll items react

I have mapped my data into timetable and showed them date wise (30 days) in horizontal scroll. I have set current date data as active element. But when the date is far like 22nd position and the view is only bound for 5 objects, how can I show the active object data (22nd object) in the center of my screen through smooth scroll on page load? (picture reference attached)
Here is my current code:
import React, { useRef, useEffect } from "react";
const DashboardData = ({
timetable,
sahriToday,
iftarToday,
currentN,
currentD,
setCurrentD,
}) => {
const handleClick = (id) => {
setCurrentD(id);
};
const dateFunc = (dhur, id) => {
let eDate = new Date(dhur);
if (currentN.getDate() === eDate.getDate()) {
setCurrentD(id);
}
}
const myRef = useRef(currentD);
useEffect(() => {
myRef.current?.scrollIntoView ({
behavior: "smooth",
block: "end"
});
}, [currentD])
console.log(currentD);
return (
<>
<div className="mother">
{timetable.map((timetable) => (
<>
<div
className={ currentD === timetable.id ? "dayboom active" : "dayboom" }
onClick={() => handleClick(timetable.id)}
ref={myRef}
>
<h3 className="weekday">{timetable.weekday}</h3>
<h3 className="monthdate">{timetable.day}</h3>
{dateFunc(timetable.edate, timetable.id)}
</div>
</>
))}
</div>
<div className="timeToday">
<div className="sahriToday">
<div>
<h2>
Sahri <span>Time</span>
</h2>
<h3>{sahriToday[0].sahri}</h3>
</div>
</div>
<div className="iftarToday">
<div>
<h2>
Iftar <span>Time</span>
</h2>
<h3>{iftarToday[0].iftar}</h3>
</div>
</div>
</div>
</>
);
};
export default DashboardData;
I have tried scrollIntoView() but that works on the full map data, not the specific one that is active.
If you don't need to save the reference for each of the elements in the map you can try adding a ref only for the element you want the function scrollIntoView do its thing. Something like:
ref={currentD === timetable.id - 2 ? myRef : null}

React app - How to show the price when the button is clicked?

I am just learning React and I have a problem. I want the price (cena) to appear only when the "Oblicz" button is clicked.
This is the component that shows the price.
function CalculatorPrice(props) {
const currentPrice = props.price;
return (
<div className='calculator__price'>
<CalculatorBtn />
<div className='calculator__price__box'>
<h3 className='calculator__price__title'>Cena</h3>
<p className='calculator__price__text'>{isNaN(currentPrice) ? <span></span> : <span>{currentPrice}</span>} zł</p>
</div>
</div>
);
}
This is the button component.
function CalculatorBtn() {
return (
<div>
<button className='calculateBtn'>Oblicz</button>
</div>
);
}
You can achieve that by passing state and a function to change that state to your button component which tracks when to show the price:
function CalculatorPrice(props) {
const currentPrice = props.price;
const [showPrice, setShowPrice] = useState(false);
const handleClick = () => {
setShowPrice(true);
}
return (
<div className='calculator__price'>
<CalculatorBtn showPrice={showPrice} handleClick={handleClick} />
<div className='calculator__price__box'>
<h3 className='calculator__price__title'>Cena</h3>
<p className='calculator__price__text'>
<span>{((isNaN(currentPrice) || !showPrice) ? '' : currentPrice) + zł}</span>
</p>
</div>
</div>
);
}
Button:
function CalculatorBtn(props) {
const handleClick = props.handleClick;
return (
<div>
<button className='calculateBtn' onClick={handleClick}>Oblicz</button>
</div>
);
}
You will need to add another way/button to reset your price (if you need to)

How to Create Show/Hide button when Looping in React

I am working on a development site and am having an issue. The issue is that I am looping over the data file in order to create some project cards. Each project card has a show more/show less button to display/hide card descriptions.
My problem is that the current setup is mapping over the data and causing it so that whenever one gets clicked, all three either open or close simultaneously. Please help me to fix this issue. Relevant code is shown below:
Data example:
{
name: "Hot in the Biscuit",
id: "3a34",
image: "/images/bonnie.jpg",
description: "A multi-page front-end business website for a local restaurant in Koh Samui, Thailand. Custom design built with vanilla JavaScript, HTML and CSS.",
link: "https://www.xxxxxxxxxxxxx.com",
date: "2021",
github: "https://github.com/xxxxxxxxxxxxxxxxxxxxxx"
},
Hero file where Showcase Component is rendered:
<h1>Featured Projects</h1>
<div>
<Showcase/>
</div>
Showcase where cards are created (UNNECCESSARY CODE REMOVED - classes and image):
const Showcase = () => {
const {readMore, setReadMore} = useContext(HeroContext)
const {toggleMenu} = useContext(NavbarContext)
return(
<>
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image/>
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{ readMore[id] ? <-- THIS IS WHERE YOU NEED AN ID
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore[id] ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub/>
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
})}
</>
)
}
export default Showcase
So I just need some help on figuring out how to set it up so that each button knows which card is being clicked and only that button open. Thank you very much for helping me. I appreciate your time and help immensely.
Bodi
It will be easier if you split showcase item to a new component.
const ShowCaseItem = ({ data }) => {
const { toggleMenu } = useContext(NavbarContext)
const [readMore, setReadMore] = useState(false)
const { id, name, image, link, github, description, date } = data;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image />
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{readMore ?
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub />
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
}
const Showcase = () => {
const { readMore, setReadMore } = useContext(HeroContext)
return (
<>
{showcase.map((item) => <ShowCaseItem data={item} />)}
</>
)
}
export default Showcase
You should update the HeroContext state to hold a reference to the ids that are shown/hidden.
Example:
const [readMore, setReadMore] = useState({});
const readMoreToggler = (id) => setReadMore(state => ({
...state,
[id]: !state[id], // <-- toggle boolean value
}));
// context value
{
readmore,
setReadMore: readMoreToggler, // pass readMoreToggler as setReadMore
}
...
const { readMore, setReadMore } = useContext(HeroContext);
...
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
...
</div>
<div>
...
<div>
<h4>
{readMore[id] // <-- check by id if toggled true|false
? description
: `${description.substring(0, 100)}...`
}
<button
onClick={() => setReadMore(id)} // <-- pass id to toggle
>
{readMore[id] ? "Show Less" : "Show More"} // <-- check by id if toggled true|false
</button>
</h4>
</div>
<div>
...
</div>
)
})}

Resources