Reputation: 145
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
Upvotes: 1
Views: 1486
Reputation: 15292
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>
);
});
Upvotes: 1
Reputation: 1590
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>
</>
)
}
}
Upvotes: 3
Reputation: 1116
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>
);
}
}
Upvotes: 2
Reputation: 7642
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>
);
}
}
Upvotes: 1
Reputation: 27
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 !
Upvotes: 2