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>
);
}
}
Related
class Pumashoe01 extends Component {
state = {
products: [
{
"_id": "1",
"title": "Puma Shoes",
"src": [
"https:h_1440,q_90,w_1080/v1/assets/images/11335328/2020/5/21/63e51b29-0e3d-4e0e-a9c5-6f2c34720ef41590079916986PumaMenNavyBlueGreenLevelIDPSneakers4.jpg",
],
"description": "Men Black Flex Fire V1 IDP Sneakers",
"content": " A pair of round-toe black sneakers, has regular styling, lace-up detail Textile upper Cushioned footbed Textured and patterned outsole.",
"price": 2364,
}
],
index: 0
};
myRef = React.createRef();
handleTab = index => {
this.setState ({ index: index })
const images = this.myRef.current.children;
for (let i = 0; i < images.length; i++) {
images[i].className = images[i].className.replace("active", "");
}
images[index].className = "active";
};
componentDidMount() {
const { index } = this.state;
this.myRef.current.children[index].className = "active";
}
constructor(props) {
super(props);
this.state = {count: 0 }
this.increment = this.increment.bind(this)
}
increment() {
this.setState( {count:this.state.count + 1},
() => {
console.log('callback value', this.state.count)})
console.log(this.state.count)}
render() {
const { products, index } = this.state;
return (
{
products.map(item => (
<div className="details" key={item._id}>
<div className="big-img">
<img src={item.src[index]} alt="" />
</div>
<div className="box">
<div className="row">
<h2>{item.title}</h2>
<span>₹{item.price}</span>
</div>
<p>{item.description}</p>
<p>{item.content}</p>
<DetailsThumb images={item.src} tab={this.handleTab} myRef={this.myRef} />
<p>You clicked {this.state.count } times</p>
<button onClick={this.increment} className="cart">Add to cart</button>
<button className="Buynow">Buy Now</button>
</div>
</div>
))
}
</div>
);
};
}
Why can't i use two state with same spelling in a class component. if i set same state spelling in two times it displays map is undefined error. If i change the state spelling in the constructor its function will not work. help me with this why dont i use two different functionality state in a single class component on reactjs.
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
Hello i am trying to maintain active classes separate separate , but its active's all list , at same time , and it should toggle active classes for separate li's...can any one help on this..
this the link of code sand box : https://codesandbox.io/s/autumn-butterfly-4wjr7?file=/src/App.js:0-650
import React, { Component } from "react";
import './styles.css'
export default class Menu extends Component {
constructor(props) {
super(props);
this.state = {
isactive: true
};
}
clicked = e => {
this.setState({
isactive: !this.state.isactive
});
};
render() {
const lits = ["Menu 1", "Menu 1", "Menu 3", "Menu 4"];
return (
<div>
{lits.map((list, key) => (
<ul key={key}>
<li className={this.state.isactive ? 'active' : 'notactive'}
onClick={this.clicked}>
{list}
</li>
</ul>
))}
</div>
);
}
}
css
active {
font-weight: 600;
color: blue
}
.notactive {
font-weight: 0;
}
You can set an index for state and used that to toggle class -
import React, { Component } from "react";
import "./styles.css";
export default class Menu extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 0
};
}
clicked = index => {
this.setState({ activeIndex: index });
};
render() {
const lits = ["Menu 1", "Menu 1", "Menu 3", "Menu 4"];
return (
<div>
{lits.map((list, key) => (
<ul key={key}>
<li
className={
this.state.activeIndex === key ? "active" : "notactive"
}
onClick={this.clicked.bind(this, key)}
>
{list}
</li>
</ul>
))}
</div>
);
}
}
Here
export default class Menu extends Component {
constructor(props) {
super(props);
this.state = {
activeKey: -1
};
}
clicked = key => {
this.setState({
activeKey: key
});
};
render() {
const lits = ["Menu 1", "Menu 2", "Menu 3", "Menu 4"];
return (
<div>
{lits.map((list, key) => (
<ul key={key}>
<li
className={this.state.activeKey === key ? "active" : "notactive"}
onClick={() => this.clicked(key)}
>
{list}
</li>
</ul>
))}
</div>
);
}
}
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
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>
));
}
}