Strange behaviour when removing item from state array - javascript

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

How to pass key to a cloned component in react using React.cloneElement()

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

How to filter an array of refs

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);

React Removing an item from a to do list

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.

Pass index as state to component using React

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

How can I pass a prop by child index in React

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>
)
}

Categories