React array item selection - arrays

I am trying to click on one card of a dynamically created list using map(). I want to click on one card from the array and add a class to it, while at the same time deselecting the other card that was previously clicked. How can I accomplish this? This is what I have so far:
const CardList = () => {
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} />
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id
}) => {
const [isSelected, setIsSelected] = useState(false);
const [clickId, setClickId] = useState('');
function handleClick(id) {
setIsSelected(!isSelected);
setClickId(id);
}
return (
<div
className={`card ${isSelected && clickId === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;

The parent component should control what card is clicked. Add className property to card component:
const Card = ({
//...
className,
onClick
}) => {
//...
return (
<div
className={`card ${className}`}
onClick={() => onClick(id)}
>...</div>
)
}
In parent component pass the className 'clicked' and add the onClick callback to set the selected card:
const CardList = () => {
const [isSelected, setIsSelected] = useState(null);
const handleClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} className={isSelected===id && 'clicked'} onClick ={handleClick} {...otherData} />
))}
</div>
);
};

You can do something like this.
First you don't have to set state to each card. Instead Lift state Up.
You define which card is selected in parent so you can pass that to children and add classes if current selected is matching that children.
const CardList = () => {
const [isSelected, setIsSelected] = useState();
const handleCardClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} handleClick={handleCardClick} isSelected={isSelected}/>
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id,
isSelected,
handleClick
}) => {
return (
<div
className={`card ${isSelected === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;

Related

How to toggle Modal and pass/change data

I am new to reacting and having trouble understanding how to pass data meta into each modal when an image is clicked and update the modal with the clicked data info. Following in my bare minimum code for sake of example
app.js
<div className="movie">
<Modal >hello world/*making sure the static text is passed into as children prop*/</Modal>
{movies.length > 0 &&
movies.map((data) => {
return <Library key={data.id} {...data} searchTerm={searchTerm} />;
})}
</div>
modal.jsx
export default function Index({children}) {
const [isOpen, setIsOpen] = useState(true)
return (
isOpen && (
<div className='modalContainer'>
<div className="modal">
<div className="close">
<button onClick={()=>{
setIsOpen(false)
}}>close</button>
</div>
{children}
</div>
</div>
)
)
}
Library.jsx
import "./Library.scss";
import {Link} from "react-router-dom";
const IMG_API = "http://image.tmdb.org/t/p/w1280";
const Library = ({
title,
poster_path,
release_date,
}) => {
return (
<div>
<Link to="/modal">
<img src={IMG_API + poster_path} alt={title} />
</Link>
<div className="meta">
<h5>{title}</h5>
<p>{release_date.slice(0, 4)}</p>
</div>
</div>
);
};
export default Library;
You should declare the isOpen state in the upper-level component so you can actually open the modal on some kind of event.
Also, you should declare a props where to pass the actual text to the Modal component:
const [isOpen, setIsOpen] = useState(false)
const handleOpen = () => setIsOpen(true);
<div className='movie'>
<Modal isOpen={isOpen} setIsOpen={setIsOpen} text='Hello, world' />
{movies.length > 0 &&
movies.map((data) => {
return <Library key={data.id} {...data} searchTerm={searchTerm} />;
})}
<button type='button' onClick={() => handleOpen()}>Open modal</button>
</div>;
You should then change your Modal declaration as:
export default function Index({ isOpen, setIsOpen, text }) {
return (
isOpen && (
<div className='modalContainer'>
<div className="modal">
<div className="close">
<button onClick={()=>{
setIsOpen(false)
}}>close</button>
</div>
{text}
</div>
</div>
)
)
}

Conditional rendering JSX

I have some lines of JSX that I want to show/hide when I click on a button.
I have initialized the state as such :
const [shown, setShown] = useState(true);
And this is the JSX :
<div className="question--section">
<div className="question--count">
<span>Question {props.class[currentQuestion].id} </span>
<h1>{props.class[currentQuestion].questionText}</h1>
</div>
</div>
I have tried to to do this :
{shown ? (
<div className="question--section">
<div className="question--count">
<span>Question {props.class[currentQuestion].id} </span>
<h1>{props.class[currentQuestion].questionText}</h1>
</div>
</div>) :
(<div>Empty</div>)}
It doesn't work. How should I approach this? ( Switching the state should hide/show the JSX)
The whole component :
import React from "react";
import { useState } from "react";
const Quiz = (props) => {
const [currentQuestion, setCurrentQuestion] = useState(0);
const [shown, setShown] = useState(true);
const handleAnswerButtonClick = () => {
if (currentQuestion + 1 < props.class.length) {
setCurrentQuestion((prevQuestion) => prevQuestion + 1);
} else {
alert("End of the quiz!");
}
};
return (
<div className="container quiz--container">
<button>Κεφάλαιο 1</button>
{shown ? (
<div>
<h1>Κεφάλαιο {props.id}</h1>
<div className="question--section">
<div className="question--count">
<span>Question {props.class[currentQuestion].id} </span>
<h1>{props.class[currentQuestion].questionText}</h1>
</div>
</div>
<div className="answer-section">
{props.class[currentQuestion].answers.map((answer) => (
<button onClick={handleAnswerButtonClick}>{answer.answerText}</button>
))}
</div>) :
(<div>Empty</div>)}
</div>
);
};
export default Quiz;
You are missing a closing </div> before the :.
It should be this:
<div className="container quiz--container">
<button>Κεφάλαιο 1</button>
{shown ? (
<div>
<h1>Κεφάλαιο {props.id}</h1>
<div className="question--section">
<div className="question--count">
<span>Question {props.class[currentQuestion].id} </span>
<h1>{props.class[currentQuestion].questionText}</h1>
</div>
</div>
<div className="answer-section">
{props.class[currentQuestion].answers.map((answer) => (
<button onClick={handleAnswerButtonClick}>{answer.answerText}</button>
))}
</div>
</div>) :
(<div>Empty</div>)}
</div>
Inside handleAnswerButtonClick callback, setShown to false or reverse it.
const handleAnswerButtonClick = () => {
setShown(false)
// or
setShown(!shown)
...
}

Toggle specific div (id) within a react component

I have a site built with post-components to show articles in a feed. Inside the component, I have a button that opens a modal onClick. I use useState to toggle on the modal which works perfectly fine. The problem is that since the toggle is put on the modal-div inside the component.. every single post modal opens whenever I click one of the buttons. I want to open only the targeted post modal (with the sam post id as the button I’m clicking). I can’t figure out how to do this…in react.
const [toggle, setToggle] = useState (true);
const toggler = () => {
setToggle(prev => !prev)
}
...
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} >
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
After trying suggested solution using object instead on bolean. I now receive this error message
[Error message][1]for the following code:
const [toggles, setToggles] = useState ({});
let id;
const createToggler = (id) = () => {
setToggles(prev => { [id] : !prev[id] })
// setToggle(prev => { ...prev, [id]: !prev[id] }) // or support multi modal at same time. but I think you don't want it.
}
const data = useStaticQuery(graphql`
query {
allMarkdownRemark (
sort: { order: DESC, fields: [frontmatter___date] }
){
edges {
node {
frontmatter {
id
title
name
details
featuredImage {
childImageSharp {
fluid(maxWidth: 800) {
...GatsbyImageSharpFluid
}
}
}
}
html
fields {
slug
}
}
}
}
}
`)
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
const id = edge.node.frontmatter.id;
const toggle = toggles[id];
const toggler = createToggler(id);
return (
<div className="post" id={edge.node.frontmatter.id}>
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} id={edge.node.frontmatter.id}>
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
[1]: https://i.stack.imgur.com/VhIYF.png
like this.
use a object instead of a single boolean.
const [toggles, setToggles] = useState ({});
const createToggler = (id) = () => {
setToggle(prev => { [id]: !prev[id] }) // atmost one id is true. others is undefine or false.
// setToggle(prev => { ...prev, [id]: !prev[id] }) // or support multi modal at same time. but I think you don't want it.
}
...
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
const id = ... // get your id form edge.
const toggle = toggles[id];
const toggler = createToggler(id);
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} >
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
I solved my problem like this
import React, {useState} from "react"
import Img from "gatsby-image"
import './posts.css';
import cancel from '../images/cancel.png'
const Post = ({title, name, id, image, details, html}) => {
const [toggle, setToggle] = useState (true);
const toggler = () => {
setToggle(prev => !prev)
}
const selectPost= (event) =>{
let id = event.target.id,
postCopy = document.getElementById('hide' + id);
toggler.call(postCopy);
}
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{title}</h2>
<h2 className="name">{name}</h2>
<button className="readMoreBtn" onClick={selectPost}>{toggle ? <h2 id={id} className="readMore">Read more</h2> : <h2 id={id} className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={image} />
<div id={'hide' + id} className={toggle ? 'hide' : 'postCopy'} >
<button aria-label="Close" onClick={selectPost} className="closeBtn">
<img alt="close-button" src={cancel}/>
</button>
<h3>{details}</h3>
<div className="copy" dangerouslySetInnerHTML= {html}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)
}
export default Post;

React <details> - have only one open at a time

I have a component with several elements. I'm trying to figure out how to update the code with hooks so that only one element will be open at a time - when a element is open, the other's should be closed. This is the code:
const HowItWorks = ({ content, libraries }) => {
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
const [open, setOpen] = useState(false);
const onToggle = () => {
setOpen(!open);
};
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${open ? "open" : "closed"}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{open ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};
Instead of having the open state be a boolean, make it be the ID of the element that is open. Then you can have a function that returns if the element is open by comparing the state with the ID.
const HowItWorks = ({ content, libraries }) => {
const [open, setOpen] = useState(0); //Use the element ID to check which one is open
const onToggle = (id) => {
setOpen(id);
};
const isOpen = (id) => {
return id === open ? "open" : "closed";
}
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${isOpen(i)}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{!!isOpen(i) ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};

How to add class to the current element in map statement

I am using a class component in react and would like to know how I can add a CSS class to the current i.e clicked element which is inside a map statement. I would like to do it using state.
<div key={q.id} id={q.id}>
<h2 className={this.state.title}>{q.title}</h2>
<h3>{q.questionText}</h3>
<div key={q.id}>
{q.options.map((opt, index) => (
<div
key={opt.id}
val={opt.val}
ref={this.options}
className={index === this.state.clickedItem ? 'myclass' : null}
onClick={() => this.setState({ clickedItem: index })}>
<p onClick={this.submitQuestion} ref={this.correctRef}>
{opt.text}
</p>
</div>
))}
</div>
Here your state
state = {clickedItem: 0}
in render
yourArray.map((el, index) => {
<div
onClick={() => this.setState({clickedItem: index})}
key={index}
className={index === this.state.clickedItem ? 'Your ClassName' : null}>
{el.name}
</div>
})
In functional with useState hook, without class.
Hope this can help.
https://codesandbox.io/s/blissful-boyd-6px43?file=/src/App.js
import "./styles.css";
/*
.is-checked {
background-color: #901c1c;
color: white;
}
*/
import React, { useState } from "react";
const App = () => {
const tags = ["portrait", "événements", "voyage", "animaux"];
const [clickedItem, setClickedItem] = useState(null);
const handleCSS = (e) => {
e.preventDefault();
let selectedTag = e ? parseInt(e.target.id, 10) : null;
setClickedItem(selectedTag);
console.log(">> clickedItem", clickedItem);
};
return (
<>
<div className="App">
<h1>Hello !</h1>
</div>
<div>
{tags.map((tag, index) => {
return (
<button
type="button"
key={index}
onClick={handleCSS}
id={index}
className={index === clickedItem ? "is-checked" : null}
>
{`#${tag}`}
</button>
);
})}
</div>
</>
);
};
export default App;

Resources