Toggle class only on one element, react js - reactjs

I`m changing class after clicking and it works.
The problem is that, classes change simultaneously in both elements and not in each one separately. Maybe someone could look what I'm doing wrong. Any help will be useful.
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false
};
}
handleToggle(e) {
this.setState({
isExpanded: !this.state.isExpanded
});
}
render() {
const { isExpanded } = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>First</strong>
</p>
</div>
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>Second</strong>
</p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;

Every element must have its seperate expanded value. So we need an array in state.
And here is the code:
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
state = {
items: [
{ id: 1, name: "First", expanded: false },
{ id: 2, name: "Second", expanded: true },
{ id: 3, name: "Third", expanded: false }
]
};
handleToggle = id => {
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
return {
...item,
expanded: !item.expanded
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
};
render() {
return this.state.items.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={el.expanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
<span> {el.expanded.toString()}</span>
</p>
</div>
));
}
}
export default PageContentSupportFaq;

You can get two state one state for first and another for a second and handle using two function like this
import React, { Component } from 'react';
class PageContentSupportFaq extends Component {
constructor(props) {
super(props)
this.state = {
isExpanded: false,
isExpanded2:false,
}
}
handleToggle(e){
this.setState({
isExpanded: !this.state.isExpanded
})
}
handleToggle2(e){
this.setState({
isExpanded2: !this.state.isExpanded2
})
}
render() {
const {isExpanded,isExpanded2} = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div onClick={(e) => this.handleToggle(e)} className={isExpanded ? "active" : "dummy-class"}>
<p className="mb-0"><strong>First</strong></p>
</div>
<div onClick={(e) => this.handleToggle2(e)} className={isExpanded2 ? "active" : "dummy-class"}>
<p className="mb-0"><strong>Second</strong></p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;

You'll need to track toggled classes in array, that way it will support arbitrary number of components:
// Save elements data into array for easier rendering
const elements = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
expanded: []
};
}
handleToggle(id) {
this.setState(state => {
if (state.isExpanded.includes(id)) {
return state.isExpanded.filter(elId => elId !== id);
}
return [...state.expanded, id];
});
}
render() {
return elements.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={this.isExpanded(el.id) ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
</p>
</div>
));
}
}

Related

Mapping of images to get particular data image on hover

Code below is an example of creation by me in code sandbox. I map sum of images and try to hover image and get that image details...if I hover on one image its getting all images data.
Can anyone help on this: https://codesandbox.io/s/romantic-haslett-qreg1?file=/src/App.js:0-1096
import React, { Component } from "react";
import "./styles.css";
export default class Images extends Component {
constructor(props) {
super(props);
this.state = {
Images: [
{ text: "sun", image: require("./images/sun.webp") },
{ text: "sky", image: require("./images/sky.jpg") },
{ text: "tree", image: require("./images/tree.jpg") }
],
hover: false
};
}
Hover = () => {
this.setState({
hover: true
});
};
NotHover = () => {
this.setState({
hover: false
});
};
render() {
return (
<div className="icon">
{this.state.Images.map((image, key) => (
<div>
<img
className="image"
onMouseEnter={this.Hover}
onMouseLeave={this.NotHover}
src={image.image}
alt=""
/>
{this.state.hover &&
<div>
<li>{image.text}</li>
</div>}
</div>
))}
</div>
);
}
}
You could save a state for hover status of each image by its key
export default class Images extends Component {
constructor(props) {
super(props);
this.state = {
Images: [
{ text: "sun", image: require("./images/sun.webp") },
{ text: "sky", image: require("./images/sky.jpg") },
{ text: "tree", image: require("./images/tree.jpg") }
],
hover: {
0: false,
1: false,
2: false
}
};
}
AddressMenuBox = (key) => () => {
this.setState({
hover: { [key]: true }
});
};
AddressMenuBoxLeave = (key) => () => {
this.setState({
hover: { [key]: false }
});
};
render() {
return (
<div className="icon">
{this.state.Images.map((image, key) => (
<div>
<img
className="image"
onMouseEnter={this.AddressMenuBox(key)}
onMouseLeave={this.AddressMenuBoxLeave(key)}
src={image.image}
alt=""
/>
{this.state.hover[key] && (
<div>
<li>{image.text}</li>
</div>
)}
</div>
))}
</div>
);
}
}
Codesandbox for implementation
You have to differentiate between the elements to force hover only on selected image, therefore you could provide additional id property to your images and change hover state to hoverTargetId so that it represents an id of selected item.
export default class Images extends Component {
constructor(props) {
super(props);
this.state = {
Images: [
{ id: 0, text: "sun", image: require("./images/sun.webp") },
{ id: 1, text: "sky", image: require("./images/sky.jpg") },
{ id: 2, text: "tree", image: require("./images/tree.jpg") }
],
hoverTargetId: null
};
}
onHoverStart = targetId => () => {
this.setState({ hover: targetId });
};
onHoverEnd = () => {
this.setState({ hover: null });
};
render() {
return (
<div className="icon">
{this.state.Images.map((image, key) => (
<div key={key}>
<img
className="image"
onMouseEnter={this.onHoverStart(image.id)}
onMouseLeave={this.onHoverEnd}
src={image.image}
alt=""
/>
{this.state.hover === image.id &&
<div>
<li>{image.text}</li>
</div>
}
</div>
))}
</div>
);
}
}
The id i've introduced could be substituted by your iteration index (key), but it's a better practice to not handle such things based on dynamically generated values.
Change your code with the boolean variable to show target image:
import React, { Component } from "react";
import "./styles.css";
export default class Images extends Component {
constructor(props) {
super(props);
this.state = {
Images: [
{ text: "sun", image: require("./images/sun.webp"), show: false },
{ text: "sky", image: require("./images/sky.jpg"), show: false },
{ text: "tree", image: require("./images/tree.jpg"), show: false }
],
hover: false
};
}
AddressMenuBox = (key) => {
let items = [...this.state.Images];
let item = { ...items[key] };
item.show = true;
items[key] = item;
this.setState({
hover: true,
Images: items
});
};
AddressMenuBoxLeave = (key) => {
let items = [...this.state.Images];
let item = { ...items[key] };
item.show = false;
items[key] = item;
this.setState({
hover: false,
Images: items
});
};
render() {
return (
<div className="icon">
{this.state.Images.map((image, key) => (
<div>
<img
className="image"
onMouseEnter={() => this.AddressMenuBox(key)}
onMouseLeave={() => this.AddressMenuBoxLeave(key)}
src={image.image}
alt=""
/>
{this.state.hover && image.show && (
<div>
<li>{image.text}</li>
</div>
)}
</div>
))}
</div>
);
}
}
Edit code on sanbox:
https://codesandbox.io/s/distracted-gagarin-54zv4?file=/src/App.js:0-1462

React - hide and show individual element

I am trying to toggle show/hide on individual elements. I thought the key={item._id} will be enough to toggle the individual items, but when clicking the toggle button every element is revealed.
This is my attempt: https://codesandbox.io/s/thirsty-torvalds-xnkt1?file=/src/App.js
I would really appreciate any suggestion. Thanks
You can split that in to a component and maintain the toggle state there which is "show". I have updated my answer in your CodeSandbox link click here
//App.js
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
import Book from './Book';
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski"
},
{
_id: "2",
book: "Witcher 2",
author: "Andrzej "
},
{
_id: "3",
book: "Witcher 3",
author: "Andrzej Sapkowski"
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
{/* <button
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.show ? <div>{item.author}</div> : null}</div> */}
<Book item={item}/>
</div>
);
});
return (
<div className="text-2xl">
<div>List of books</div>
{listItem}
</div>
);
}
}
// Book.js
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false
};
}
handleShow = () => {
this.setState({
show: !this.state.show
});
};
render(){
return(
<>
<button
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.show ? <div>{this.props.item.author}</div> : null}</div>
</>
)
}
}
You can just change the show variable in the state to take the index of the element like this :
handleShow = (idx) => {
this.setState({
show: idx
});
};
and your constructor becomes :
constructor(props) {
super(props);
this.state = {
show: -1
};
}
And your list becomes :
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
onClick={() => this.handleShow(item._id)}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.show === item._id ? <div>{item.author}</div> : null}</div>
</div>
);
});
I think that will do it !
you can save list into state and add a isToggle prop for every object, and handleShow change the isToggle state of clicked target item, code will look like this:
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski",
isToggle: false,
},
{
_id: "2",
book: "Witcher 2",
author: "Andrzej ",
isToggle: false,
},
{
_id: "3",
book: "Witcher 3",
author: "Andrzej Sapkowski",
isToggle: false,
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
list
};
}
handleShow = (e) => {
const index = this.state.list.findIndex(t => t._id === e.currentTarget.dataset.id);
const list = [...this.state.list];
list[index].isToggle = !list[index].isToggle;
this.setState({ list });
};
render() {
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
data-id={item._id}
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{item.isToggle ? <div>{item.author}</div> : null}</div>
</div>
);
});
return (
<div className="text-2xl">
<div>List of books</div>
{listItem}
</div>
);
}
}
Updated and working code here
Changes in code:
In list data structure
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski",
show: false // add additional key to toggel
},
...
];
In constructor
constructor(props) {
super(props);
this.state = {
books: list // defined books state
};
}
in handleShow
handleShow = event => {
console.log(event.target.id);
const books = this.state.books;
const bookId = event.target.id; // get selected book id
for (let index = 0; index < books.length; index++) {
if (bookId === books[index]._id) {
books[index].show = !books[index].show; //toggle the element
}
}
this.setState({ books });
};
In render:
render() {
const listItem = this.state.books.map(item => { //use state to iterate
return (
<div key={item._id} className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
id={item._id} //add key to get the selected book
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{item.show ? <div>{item.author}</div> : null}</div> // use key `show` to toggle it
</div>
);
});
if you want to show them toggled individually you can do like this:
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski"
},
{
_id: "2",
book: "Witcher 2",
author: "Andrzej "
},
{
_id: "3",
book: "Witcher 3",
author: "Andrzej Sapkowski"
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: false,
showId: null,
};
}
handleShow = (id) => {
this.setState({
showId: id
});
};
render() {
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
onClick={() => this.handleShow(item._id)}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.showId === item._id ? <div>{item.author}</div> : null}</div>
</div>
);
});
return (
<div className="text-2xl">
<div>List of books</div>
{listItem}
</div>
);
}
}

React - Make an FAQ class toggle on click

I am creating an FAQ with React and I have the questions in strong tags and the answers in p tags. On click of the strong tags I would like to add a class of active to the clicked tag. I am close but there is some sort of scope issue on my toggle function and I'm not sure how to move past it:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Here's what I have so far
You need to bind toogleClass method to the Questions instance:
Opition one: Using bind
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Option 2: Using class property initializer
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass = () => {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
To selected only the active question you can do this:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
activeQuestion: null
};
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
data-question={item.question}
className={
item.question === this.state.activeQuestion ? "active" : null
}
onClick={() => {
console.log(item.question);
this.setState({ activeQuestion: item.question });
}}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Your function toggleClass needs to be an arrow function -> toggleClass = () => {...your code here...}. When it's a regular function the outer scope (where this.state is), is not being passed to your function. Without an arrow function, when you refer to this you are referring only to the scope of the toggleClass function, where state does not exist and so is undefined.
Working Code
Also due to setState being async, it's best practice to use current state by referencing it within the setState function like below:
toggleClass = () => {
this.setState(prevState => ({
active: !prevState.active
})
}
When you reference state outside of the setState function then pass it in, it's possible that the state would be different by the time you use it to set it.
i.e. say the currentState you got was true, and then your setState uses current state to set the opposite (False) by the time setState tries to set it, something else might have changed your existing state to False already and you are just setting it to False again (instead of modifying what the current state is at that time which would be False and you want want True). Unlikely in your case, but it is good practice to follow this because you might run into this issue elsewhere

How to loop this state in React?

I try to loop my state with a .map but it didn't work (I don't see my log in the console) ...
{this.state["cards"].map(card => console.log(card.title))}
Please tell me what's the error...
I have my state here (which a see the log in my console) :
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
cards: []
};
fs.readdir(testFolder, (err, files) => {
files.forEach(file => {
this.state.cards.push({
title: "Test1",
pic:
"https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png",
content: "Content",
link: "#"
});
});
});
console.log(this.state); // I see this one
}
Here is my console log :
Object {cards: Array[2]}
cards: Array[2]
0: Object
title: "Test1"
pic: "https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png"
content: "Content"
link: "#"
1: Object
title: "Test1"
pic: "https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png"
content: "Content"
link: "#"
EDIT : Here is my render :
render() {
return (
<div>
<div className="container-fluid text-center">
<h2 className="h2_title">Hi.</h2>
</div>
<div className="main">
<ul className="cards">
{this.state["cards"].map(cards => (
<Card
key={cards.title}
link={cards.link}
title={cards.title}
pic={cards.pic}
/>
))}
</ul>
</div>
</div>
);
}
He doesn't display my cards and if I replace it by console.log it didn't display it in my console
Thanks for helping me !
You need to make your operations inside the componentDidMount and update the state like this:
import React, {Component} from "react";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
cards: [],
loading: true
};
}
componentDidMount() {
let cards = [];
fs.readdir(testFolder, (err, files) => {
files.forEach(file => {
cards.push({
title: "Test1",
pic:
"https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png",
content: "Content",
link: "#"
});
});
this.setState({
cards,
loading: false
})
});
}
render() {
const {cards, loading} = this.state;
if (loading) {
return (
<div>Getting Files, please wait...</div>
)
}
if (cards.length === 0) {
return (
<div>No files found</div>
)
}
return (
<div>
<div className="container-fluid text-center">
<h2 className="h2_title">Hi.</h2>
</div>
<div className="main">
<ul className="cards">
{cards.map(card => (
<Card
key={card.title}
link={card.link}
title={card.title}
pic={card.pic}
/>
))}
</ul>
</div>
</div>
);
}
}
You shouldn't directly change the state like this this.state.cards.push

React JS - Event Handler in a dynamic list

I'm bringing a API s' content based on a dynamic list and I'm trying to apply a mouserEnter on each li. The event results by toggling content in the each list item. The event is working but it is toggling content in all the list items all at once, but I want it to toggle only the content that matches with the list item that is receiving the mouseEnter.
import _ from 'lodash';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class Dribbble extends React.Component {
constructor(props) {
super(props);
this.state = {
work: [],
hover: false
};
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
handleMouseEnter(){
this.setState({ hover: true })
}
handleMouseLeave(){
this.setState({ hover: false })
}
componentDidMount() {
this.ShotList();
}
ShotList() {
return $.getJSON('https://api.dribbble.com/v1/shots?per_page=3&access_token=41ff524ebca5e8d0bf5d6f9f2c611c1b0d224a1975ce37579326872c1e7900b4&callback=?')
.then((resp) => {
this.setState({ work: resp.data.reverse() });
});
}
render() {
const works = this.state.work.map((val, i) => {
return <li key={i} className="box"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{!this.state.hover ?
<div>
<img className="cover" src={val.images.normal} />
<div className="bar">
<h2>{val.title}</h2>
<span>{val.views_count}</span>
<i className="fa fa-eye fa-2x" aria-hidden="true"></i>
</div>
</div>
: null}
{this.state.hover ?
<div>
<h3>{val.user.name}</h3>
<img className="avatar img-circle" src={val.user.avatar_url}/>
<p>{val.description}</p>
</div>
:
null
}
</li>
});
return <ul>{works}</ul>
}
}
Here is my code:
There are couple of issues in your example, firstly as #aherriot states you should move the ul outside the map.
Next i would set this.state.hover to be the id of the item being hovered over on onMouseEnter.
The below snippet shows a basic example of this working that should be easy enough to adapt to your code.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [{id: 1, name: 'Fred'}, {id: 2, name: 'Albert'}, {id: 3, name: 'Jane'}],
hover: false,
}
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.renderItem = this.renderItem.bind(this);
}
handleMouseEnter(id){
console.log(`handleMouseEnter this.setState({ hover: ${id} })`);
this.setState({ hover: id })
}
handleMouseLeave(){
console.log('handleMouseLeave this.setState({ hover: false })');
this.setState({ hover: false })
}
renderItem(item, index) {
let content = [];
content.push(
<span>ID: {item.id}, Name: {item.name}</span>
);
if(this.state.hover === item.id) {
console.log('display " - hovering" for item id: ' + item.id);
content.push(
<span> - hovering</span>
);
}
return (
<li key={item.id}
onMouseEnter={() => this.handleMouseEnter(item.id)}
onMouseLeave={this.handleMouseLeave}
>
{content}
</li>
)
}
render() {
return <ul>
{this.state.items.map(this.renderItem)}
</ul>
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="root"></div>
Maybe you should move the <ul> tag outside of this.state.work.map You only want one <ul> to show up, not one for each element.
You can place it at the bottom inside your div tag instead: return (<div><ul>{works}</ul></div>)

Resources