Reputation: 17
good people.
i have a small program that adds table row elements, when "Add" is clicked. There is also an color change option when table cell is clicked. The problem is - when multiple elements are created, clicking on one of them, changes the color for all of them, though i have a onClick sitting only on the TD tags. How could this be fixed?
https://jsfiddle.net/mattighof/5nmcyL7b/
<table>
{this.state.list.map((element, index) => {
return (
<tr>
<td
className={this.state.textColor ? "trRed" : "trBlack"}onClick={this.handleClick}>
{element}
<div
onClick={e => this.removeElement(e, index)}
className="div"
/>
</td>
</tr>
);
})}
</table>
would highly appreciate your advice.
Upvotes: 0
Views: 171
Reputation: 46
Since you're generating <td>
elements in an anonymous function, using this
inside it will refer to the parent closure, which, in this case, is the Table
component. Therefore, the textColor
property is local to the component itself, and not to the individual <td>
elements.
You're already iterating through a list that you keep in the component state, so you can slightly change the element structure to allow you to keep arbitrary state data individually.
To do this, instead of adding a raw string to your list, add an object with the text
and isSelected
properties set to the desired values, and when rendering the <td>
elements or changing colors, use the said properties. You can, of course, name these properties to your liking, and even add more properties to individually manage the states of your elements.
One other thing to note is that the current implementation of the handleClick
function is unaware of the context that you're calling it from, so you'll also need to pass the index
of the element when calling it, and update your state with a new list
where the element at the specified index has its state updated.
Here's the revised functions as per my naming:
addElement() {
this.setState({
list: this.state.list.concat({
text: "element",
isSelected: false
})
});
}
handleClick(e, index) {
if (!this.state.list[index]) {
return;
}
// to avoid any side effects, we're taking the immutable data approach
// and creating a new list with intended values, rather than updating the list directly
const oldElement = this.state.list[index];
const newElement = Object.assign({}, oldElement, {isSelected: !oldElement.isSelected});
const newList = [].concat(this.state.list);
newList.splice(index, 1, newElement);
this.setState({
list: newList
});
}
...
{this.state.list.map((element, index) => {
return (
<tr>
<td
className={element.isSelected ? "trRed" : "trBlack"}
onClick={e => this.handleClick(e, index)}
>
{element.text}
<div
onClick={e => this.removeElement(e, index)}
className="div"
/>
</td>
</tr>
);
})}
...
I've also forked your fiddle and updated it with the code blocks I mentioned above: https://jsfiddle.net/w76frtgx/
Upvotes: 1
Reputation: 3241
There are many ways to handle this. Instead of relying on state to change the class, simply toggle the class trRed
on the clicked target element.
To achieve this, modify handleClick
to this:
handleClick(e) {
e.target.classList.toggle("trRed")
}
Edit the style rule trRed
to this:
.trRed {
color: red;
}
And finally remove the textColor: true
from state
since it will no longer be used.
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
list: []
};
this.handleClick = this.handleClick.bind(this);
this.addElement = this.addElement.bind(this);
this.removeElement = this.removeElement.bind(this);
}
handleClick(e) {
e.target.classList.toggle("trRed")
}
addElement() {
this.setState({ list: this.state.list.concat("element") });
}
removeElement(e, index) {
e.stopPropagation();
this.setState({ list: this.state.list.filter((_, i) => index !== i) });
}
render() {
return (
<div className="container">
<button onClick={this.addElement} type="button">
Add
</button>
<table>
{this.state.list.map((element, index) => {
return (
<tr>
<td
onClick={this.handleClick}
>
{element}
<div
onClick={e => this.removeElement(e, index)}
className="div"
/>
</td>
</tr>
);
})}
</table>
</div>
);
}
}
ReactDOM.render(<Table />, document.getElementById("app"));
body {
padding: 20px;
}
td {
border: 1px solid black;
height: 15px;
padding: 5px;
}
tr {
border: 1px solid black;
position: relative;
}
table {
margin-top: 10px;
text-align: center;
width: 70px;
border: 1px solid black;
background-color: beige;
border-collapse: collapse;
}
.trRed {
color: red;
}
.div {
float: right;
width: 6px;
height: 6px;
background-color: red;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Upvotes: 0