I have an array in state which contains various components. When I click the remove button on one of the components, it removes the first item from the array instead. I only seem to have this problem when using components in the array, it works fine with an array of strings.
Parent component:
addItem = (block) => {
const add = [...this.state.items, block];
this.setState({items: add})
}
removeItem = (index) => {
const remove = [...this.state.items];
remove.splice(index, 1);
this.setState({items: remove})
}
render(){
return(
<div className="Board">
<div className="BoardContainer">
{this.state.items.map((item, index) => { return <Item remove= {this.removeItem} key={index} >{item}</Item>})}
</div>
<button onClick={() => this.addItem(<BannerImage />)}>Banner Image</button>
<button onClick={() => this.addItem(<ShortTextCentered />)}>Short Text Centered</button>
</div>
)
}
Child component:
export class Item extends React.Component {
handleRemove = () => {
this.props.remove(this.props.index)
}
render() {
return (
<div className="item">
{this.props.children}
<button onClick={this.handleRemove} >Remove</button>
</div>
)
}
}
You used inside your component 'this.props.index' but you didn't pass the index to your component.
you should do something like this:
{this.state.items.map((item, index) => { return <Item remove={this.removeItem} key={index} index={index} >{item}</Item>})}
Array.prototype.splice() mutates the array, so it's better not to use splice() with React.
Easiest to use Array.prototype.filter() to create a new array.
Furthermore working with unique id's rather than indexes prevents from unexpected results.
let filteredArray = this.state.item.filter(x=> x!== e.target.value)
this.setState({item: filteredArray});
Related
I have a tabs component that accepts the elements array as props and I am trying to assign key to cloned elements by using React.cloneElement() with no luck. I am passing down elements as an array. Mapping over that elements array, cloning each component in that array and trying to assign key to each element.But, I still keep getting the warning about each component should have a key prop. How can I assign keys to each component by using React.cloneElement()?
Below is my elements array that I am passing to my Tabs component.
Below is my tabs component:
import React, {useState, useEffect, useCallback} from 'react';
import classes from './tabs.module.css';
const Tabs = ({ tabsData, active = 0 }) => {
const [activeTab, setActiveTab] = useState(active);
const setActiveTabIndex = useCallback((index) => {
setActiveTab(index)
}, [activeTab])
return (
<div className={classes.tabsContainer}>
<ul className={classes.tabsList}>
{
tabsData.map((item, index) => (
<li className={`${classes.tab} ${index === activeTab && classes.activeTab}`} onClick={() => setActiveTabIndex(index)}>{ item.tabTitle }</li>
))}
</ul>
<div className={classes.textContainer}>
{tabsData[activeTab].content.map((ElementItem, index) => (
React.cloneElement(ElementItem, {key:index})
))}
</div>
</div>
)
}
export default Tabs
Below is the array that I am passing down to that tabs component.
const tabsData = [
{
tabTitle: "Description",
content: [<h1 className={classes.tabTitle}>{'Product Description'}</h1>, productDetails.shortDescription.html ? <RichContent html={productDetails.shortDescription.html} /> : <RichContent html={"<p>No short description found</p>"} />]
},
{
tabTitle: "Attributes",
content: [<h1 className={classes.tabTitle}>{'Product Attributes'}</h1>, <CustomAttributes customAttributes={customAttributesDetails.list} />]
}
]
I have also tried the following to try to assign key to each element.
<React.Fragment key={index}>
{ React.cloneElement(ElementItem) }
</React.Fragment>
Problem is in this part of the code
<ul className={classes.tabsList}>
{tabsData.map((item, index) => (
<li key={index} className={`${classes.tab} ${index === activeTab &&
classes.activeTab}`} onClick={() => setActiveTabIndex(index)}>{ item.tabTitle }</li>
))}
</ul>
You should also add key prop to each li element
I have an array of data that i map over and display on screen. i want to be able to do stuff to these dom elements when clicked. I've stored the refs in an array and now onclick i want to do something with the one that was clicked and something else with the rest.
So i thought of using es6 filter to remove the current one from a new array and then iterate over them. And then i'm free to do whatever i want with the item that was clicked.
However i can't get the filter to work. doesn't console anything.
https://codepen.io/_adamjw3/pen/MWWmGEg
class App extends React.Component {
constructor(props) {
super();
this.myRefs = [];
this.state = {
testData: [
"dave",
"pete",
"mark"
]
}
}
myActionHandler = key => {
const selectedDomElement = this.myRefs[key];
const filtered = this.myRefs.filter(item => item !== item);
filtered.forEach(function(entry) {
console.log("all but selected ne", entry);
});
};
render(){
return (
<div className="container">
{this.state.testData.map((item, key) => {
return (
<div key={key} >
<button onClick={() => this.myActionHandler(key)} ref={ref => (this.myRefs[key] = ref)} >
{item}
</button>
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
It seems like your only issue is that you're filter function is wrong. It should use the selectedDomElement.
const filtered = this.myRefs.filter(item => item !== selectedDomElement);
i'm creating a simple react to do list, I'm currently working on a delete button, I have created an array then passed this array into a prop, I then need to splice that item from the prop array when the user clicks the delete button. I was able to store the array number but I cant seem to update the array after its deleted.
CLASS CALL:
<TodoList items={this.state.items} deleteItems={this.deleteItem}/>
SUB-CLASS CODE:
class TodoList extends Component {
constructor(props) {
super(props);
this.removeItem = this.removeItem.bind(this);
}
render() {
return (
<div>
{ this.props.items.map((item, i) => (
<div className={"col-12"} key={item.id}>
<div className={"card text-white"}>
<div className={item.priority}>
<div className={"col-12 card-body"}>
<h1>{item.title}</h1>
<p>{item.text}</p>
<button onClick={() => { this.removeItem(item, i)}} key={i} className={"col-12 btn btn-primary bg-red"}>Delete</button>
</div>
<div/>
</div>
</div>
</div>
))}
</div>
);
}
removeItem(e, i) {
this.props.items.splice(i, '');
console.log(i);
}
}
I have been looking at different stack questions but none of the solutions seem to apply to this, thanks for any constructive feedback :)
I believe <TodoList /> component should have its own state. However, if you can't do so, there's 2 solutions to this problem:
Keep <ToDoList /> component's state and props in sync (In case the parent component modifies the state passed down as items). Then modify the <TodoList /> 's state.
Declare a method that removes the item inside the parent component which has the
state, and pass it down as props (Recommended)
Example code:
class ParentComponent extends Component {
state = {
items: [1, 2, 3]
}
removeItem = index => () => {
this.setState(prevState => ({
items: prevState.items.filter((_, i) => i !== index) //Filter the items
}));
};
render() {
return (
<TodoList items={this.state.items} deleteItems={this.removeItem} />
);
}
}
Important: Always use pure functions to modify the state. Do not use .splice() or .push() (If you haven't cloned the state yet). It's always safer to use .filter(), .map(), .concat(), etc.
I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910
I'm trying to develop a generic container for React, that would work like this:
<PanelContainer>
<PanelConsole />
<PanelMemory />
<PanelLog />
</PanelContainer>
I want to dynamically create a tab system within the container, this works as follows:
renderTabs = () => {
return (
<ul className="panel_tabs">
{React.Children.map(this.props.children, (child, i) =>
<li key={child.type.display_name} onClick={() => this.handleClickTab(i)}>
{child.type.display_name}
</li>
)}
</ul>
);
}
This allows me to render the tabs with the display_name property within the class. This so far works, but now I'm trying to get the click to work. I want it to work dynamically so I don't have to build specialized containers for each instance of the panel. I'd ideally like to set the property of a child in this.props.children by index, so for example:
this.props.children[0].props.shown = false;
Is this possible?
I think React.Children.map and React.cloneElement works for you:
render() {
const { children } = this.props;
const tabs = this._renderTabs();
const childrenWithProps = React.Children.map(children, (child, id) =>
React.cloneElement(child, { shown: this.state.shows[i] }));
return (
<div>
<div>{tabs}</div>
<div>{childrenWithProps}</div>
</div>
)
}