How to delete an element in list in react? - reactjs

i am trying to do a simple toDo app with react. I couldnt do how to delete an element in list. Here my code; first state:
class AppForm extends Component {
constructor(props) {
super(props);
this.state = { items: [] , text:''};
this.onChangeHandler=this.onChangeHandler.bind(this)
this.submitHandler=this.submitHandler.bind(this)
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
};
this.setState(state => ({
items: state.items.concat(arrayItem),
text: ''
}));
}
here the problem area. I also tried splice but couldnt.
deleteItem=(index)=>{
let todos= this.state.items.filter(todo => index !== todo.key)
this.setState({
items : todos
})
}
then rendering..
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input type="text" onChange={this.onChangeHandler} value={this.state.text}/>
</form>
<ul>
{this.state.items.map((item,index) =>{
return (
<li key={index}> {item.text}
<p onClick={this.deleteItem.bind(this,index)}> X </p>
</li>
)
})}
</ul>
</div>
);
}
}
export default AppForm;

Splice is the answer.
First, I create a copy of your state array. Then splice it using the index clicked. Then set setState with the spliced array.
deleteItem=(index)=>{
let todos= [...this.state.items]
todos.splice(index, 1)
this.setState({
items : todos
})
}

deleteItem = (index) => {
this.setState(({items}) => {
return {items: [...items.filter(todo => index !== todo.key)]};
})
}

First of all you're not setting the key anywhere when you are inserting in array. It is not at all recommended to use index as key in array. It should be unique.
const arrayItem = {
text: this.state.text,
id: uuid()
};
So I've added the uuid and compared with the id of the element.
codesandbox
uuid
// UNIQUE KEY GENERATOR
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export default uuidv4;
React component
import React, { Component } from "react";
import uuid from "./uuid";
import "./styles.css";
class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: "" };
this.onChangeHandler = this.onChangeHandler.bind(this);
this.submitHandler = this.submitHandler.bind(this);
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
id: uuid()
};
this.setState((state) => ({
items: state.items.concat(arrayItem),
text: ""
}));
};
deleteItem = (key) => {
let todos = this.state.items.filter((todo) => key !== todo.id);
this.setState({
items: todos
});
};
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input
type="text"
onChange={this.onChangeHandler}
value={this.state.text}
/>
</form>
<ul>
{this.state.items.map((item) => {
return (
<li key={item.id}>
{item.text}
<p onClick={() => this.deleteItem(item.id)}> X </p>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;

Related

toggle between sorted list and original list React.js

I'm trying to toggle between a descending ordered list then back to the original list that was rendered before sorting the list. The code is currently changing the original list upon clicking the sort button but does not return to the original list when clicked again. It stays in the sorted order.
class NonProfitContainer extends Component {
state = {
asc: true
}
toggleSort = () => {
let originalList = this.props.nonprofits.map(np => <NonprofitList key={np.id} nonprofit={np}/>)
let sortedList = this.props.nonprofits.sort((a, b) => b.name.localeCompare(a.name));
this.setState({
nonprofits: this.state.asc
? originalList
: sortedList,
asc: !this.state.asc,
});
};
render(){
const { asc } = this.state
return(
<div className="container">
<button onClick={() => this.toggleSort()}>{asc ? 'Sort Z-A' : 'Back'}</button>
<hr/>
{this.props.nonprofits.map(np => <NonprofitList key={np.id} nonprofit={np}/>)}
<hr/>
<h3>Add A New Nonprofit:</h3><br/>
<NonprofitForm />
</div>
)
}
}
const mapStateToProps = state => {
return {
nonprofits: state.nonprofitReducer.nonprofits,
}
}
export default connect(mapStateToProps,{getNonprofits})(NonProfitContainer)
Any advice would be appreciated! I'm pretty new to react.
You are sorting the props.nonprofits directly, you need to copy the values to another variable, sort it and if you want the original one, just copy that one.
You might need to use componentDidUpdate if you are getting values from redux to setState, just google it and you will get the idea. This code will cover your sorting issue.
https://codepen.io/ktdev/pen/abBwzvo
const NonprofitList = (props) => {
return <h1>{props.nonprofit.id}</h1>;
};
class Card extends React.Component {
static defaultProps = {
nonprofits: [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
{ id: 3, name: "c" }
]
};
constructor(props) {
super(props);
this.state = {
asc: true,
nonprofits: props.nonprofits
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ darkMode: !this.state.darkMode });
}
toggleSort = () => {
let sortedList = [...this.props.nonprofits];
sortedList.sort((a, b) => b.name.localeCompare(a.name));
this.setState({
nonprofits: this.state.asc ? sortedList : this.props.nonprofits,
asc: !this.state.asc
});
};
render() {
const { asc } = this.state;
return (
<div className="container">
<button onClick={() => this.toggleSort()}>
{asc ? "Sort Z-A" : "Back"}
</button>
<hr />
{this.state.nonprofits.map((np) => (
<NonprofitList key={np.id} nonprofit={np} />
))}
<hr />
<h3>Add A New Nonprofit:</h3>
<br />
</div>
);
}
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title="Example Component" />, el);

I think render works twice

I'm only learning React, trying to write a simple TODO list app. When I'm trying to add a new task, two identical tasks are added. I tried to debug by the console.log element and saw a problem. render works twice, so my button sends info to the function twice. Can someone please guide me to the solution? Here is the code.
import React from 'react';
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
}
addTask = () => {
const { input } = this.state;
if (input) {
this.props.addTask(input);
this.setState({ input: '' });
}
};
handleEnter = event => {
if (event.key === 'Enter') this.addTask();
};
inputChange = event => {
this.setState({ input: event.target.value });
};
render() {
const { input } = this.state;
console.log(this.state);
return (
<div className="task-input">
<input
type="text"
onKeyPress={this.handleEnter}
onChange={this.inputChange}
value={input}
></input>
<button onClick={this.addTask } >ADD</button>
</div>
);
}
}
export default TaskInput;
Here is the App.js code:
import React from 'react';
import Task from './components/Task';
import TaskInput from './components/TaskInput';
class App extends React.Component {
constructor () {
super();
this.state = {
tasks: [
{id: 0, title: 'Create Todo-app', done: false},
{id: 1, title: 'Do smth else', done: true},
{id: 2, title: 'Do more things', done: false}
]
};
}
addTask = task => {
this.setState(state => {
let {tasks} = state;
console.log("state");
tasks.push({
id: tasks.length !==0 ? tasks.length : 0,
title: task,
done: false
});
return tasks;
});
}
doneTask = id => {
const index = this.state.tasks.map(task => task.id).indexOf(id);
this.setState(state => {
let {tasks} = state;
tasks[index].done = true;
return tasks;
});
};
deleteTask = id => {
const index = this.state.tasks.map(task => task.id).indexOf(id);
this.setState(state => {
let {tasks} = state;
delete tasks[index];
return tasks;
})
};
render() {
const { tasks } = this.state;
const activeTasks = tasks.filter(task => !task.done);
const doneTasks = tasks.filter(task => task.done)
return (
<div className = "App">
<h1 className="top">Active tasks: {activeTasks.length}</h1>
{[...activeTasks, ...doneTasks].map(task => (
<Task
doneTask={() => this.doneTask(task.id)}
deleteTask={() => this.deleteTask(task.id)}
task={task}
key={task.id}
></Task>))}
<TaskInput addTask={this.addTask}></TaskInput>
</div>
);
}
}
export default App;
I think you are accidentally directly modifying the state inside addTask.
The line let {tasks} = state; is creating a reference to the original state, rather than a new copy, and then your push modifies the state directly.
Using expansion/spread syntax to get a copy of your array like this should work:
addTask = task => {
this.setState(state => {
const tasks = [ ...state.tasks ];
tasks.push({
id: tasks.length !==0 ? tasks.length : 0,
title: task,
done: false
});
return { tasks };
});
}
Using let tasks = [ ...state.tasks ]; will create a new array rather than a reference, and prevent the state from being modified directly.
The reason you were seeing double results was that you effectively set the state with the push, and then set it again with the returned value.
I've changed your code a little bit. It's working here. Would you please check?
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: "",
tasks: []
};
}
addTask = newTask => {
this.setState(state => ({
...state,
input: "",
tasks: [...state.tasks, newTask]
}));
};
handleEnter = event => {
if (event.key === "Enter") this.addTask(event.target.value);
};
inputChange = event => {
this.setState({ input: event.target.value });
};
render() {
const { input } = this.state;
console.log(this.state);
return (
<div className="task-input">
<input
onKeyPress={this.handleEnter}
onChange={this.inputChange}
value={input}
></input>
<button onClick={this.addTask}>ADD</button>
</div>
);
}
}
ReactDOM.render(<TaskInput/>, document.querySelector("#root"));
.as-console-wrapper {
max-height: 5px;
}
<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="root"></div>

React click on filtered List

I'm new to react. will really appreciate some help.
I have a list that I want to filter, and I want for each item in the list to click on it so it will open a page on the right side.
currently, the click is causing the list to disappear. I think I have an issue on the filtered list.
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
MyList: [
'A',
'B',
'C'
],
filter: "",
selectedItem: ""
}
this.handleClick = this.handleClick.bind(this);
}
handleFilter = (newFilter) => {
this.setState(() => ({
filter: newFilter
}));
}
handleClick(selectedItem) {
this.setState((prevstate) => ({
MyList: selectedItem
}));
}
render() {
const filteredList = this.state.MyList.filter(section =>
section.toLowerCase().includes(this.state.filter.toLowerCase()))
return (
<div>
{<Filter handleFilter={this.handleFilter} />}
{filteredList.map((listItem, i) =>
<p onClick={() => this.handleClick(i)}>{listItem}</p>)}
</div>
)
}
}
const Filter = (props) => (
<div>
<input name="filter" onChange={(e) => {
props.handleFilter(e.target.value);
}} />
</div>
);
ReactDOM.render(<MyApp />, document.getElementById('root'));
In your onClick function aren't you resetting your list from an initial array to a string? So on re-rendering you'll have a string as MyList. It should be:
handleClick(selectedItem) {
this.setState((prevstate) => ({
MyList: [selectedItem]
}));
}

Showing two different components based on return value in react js

I have search function where on entering text it returns the object from an array(json data) and based on the condition (whether it as an object or not) I need to show two different components ie. the list with matched fields and "No matched results found" component.
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
searchTextData: '',
isSearchText: false,
isSearchOpen: false,
placeholderText:'Search Content',
filteredMockData: [],
dataArray: []
};
}
handleSearchChange = (event, newVal) => {
this.setState({ searchTextData: newVal })
if (newVal == '') {
this.setState({ clearsearch: true });
this.setState({
filteredMockData: []
});
this.props.onDisplayCloseIcon(true);
} else {
this.props.onDisplayCloseIcon(false);
searchData.searchResults.forEach((item, index, array) => {
this.state.dataArray.push(item);
});
this.setState({ filteredMockData: this.state.dataArray });
}
}
clearInput = () => {
this.setState({ searchTextData: '' })
}
isSearchText = () => {
this.setState({ isSearchText: !this.state.isSearchText });
}
onSearchClick = () => {
this.setState({ isSearchOpen: !this.state.isSearchOpen });
this.setState({ searchTextData: '' });
this.props.onDisplayCloseIcon(true);
}
renderSearchData = () => {
const SearchDatasRender = this.state.dataArray.map((key) => {
const SearchDataRender = key.matchedFields.pagetitle;
return (<ResultComponent results={ SearchDataRender } /> );
})
return SearchDatasRender;
}
renderUndefined = () => {
return ( <div className = "search_no_results" >
<p> No Recent Searches found. </p>
<p> You can search by word or phrase, glossary term, chapter or section.</p>
</div>
);
}
render() {
return ( <span>
<SearchIcon searchClick = { this.onSearchClick } />
{this.state.isSearchOpen &&
<div className = 'SearchinputBar' >
<input
placeholder={this.state.placeholderText}
className= 'SearchInputContent'
value = { this.state.searchTextData}
onChange = { this.handleSearchChange }
/>
</div>
}
{this.state.searchTextData !== '' && this.state.isSearchOpen &&
<span className='clearText'>
<ClearIcon className='clearIcon' clearClick = { this.clearInput }/>
</span>
}
{this.state.searchTextData !== '' && this.state.isSearchOpen &&
<div className="SearchContainerWrapper">
<div className = "arrow-up"> </div>
<div className = 'search_result_Container' >
<div className = "search_results_title" > <span> Chapters </span><hr></hr> </div>
<div className="search_show_text" >
<ul className ="SearchScrollbar">
{this.state.filteredMockData.length ? this.renderSearchData() : this.renderUndefined() }
</ul>
</div>
</div>
</div>}
</span>
);
}
}
Search.propTypes = {
intl: intlShape.isRequired,
onSearchClick: PropTypes.func,
isSearchBarOpen: PropTypes.func,
clearInput: PropTypes.func,
isSearchText: PropTypes.func
};
export default injectIntl(Search);
Search is my parent component and based on the matched values I need to show a resultComponent like
class ResultComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render(){
console.log(this.props.renderSearchData(),'Helloooo')
return(<p>{this.props.renderSearchData()}</p>)
}
}
ResultComponent.propTypes = {
results: PropTypes.string.isRequired
};
I'm getting an error "renderSearchData is not an function".I'm new to react and Hope someone can help.
The only prop passed to ResultComponent component is results
So in ResultComponent Component Replace
this.props.renderSearchData()
With
this.props.results

Reactjs setState not updating for this one function only

For this application, clicking a listed item once should create a button component underneath this listed item. Clicking the button should cause this listed item to be deleted.
I am currently facing difficulty trying to 'delete' the listed item after the button is clicked. Here is the code that went wrong (this is found in CountdownApp component) :
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList) // countdownList array is correct
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns) // this.state.countdowns does not match countdownList
console.log(countdownList) // countdownList array is still correct
})
}
In the code above, I removed the item to be deleted from countdownList array with splice and tried to re-render the app with setState. However, the new state countdowns do not reflect this change. In fact, it returns the unedited state.
I have also tried the following:
handleDelete(index) {
this.setState({
countdowns: [] // just using an empty array to check if setState still works
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
In the code above, I tried setting state to be an empty array. The console log for this.state.countdowns did not print out an empty array. It printed out the unedited state again
This is the only event handler that isn't working and I have no idea why (main question of this post) :/
If I have 'setstate' wrongly, why does the other 'setState' in other parts of my code work?? (I would like to request an in-depth explanation)
This is all my code for this app (its a small app) below:
import React from 'react'
import ReactDOM from 'react-dom'
class DeleteButton extends React.Component {
render() {
return (
<ul>
<button onClick={this.props.onDelete}>
delete
</button>
</ul>
)
}
}
class Countdown extends React.Component {
render () {
//console.log(this.props)
return (
<li
onClick={this.props.onClick}
onDoubleClick={this.props.onDoubleClick}
>
{this.props.title} - {this.props.days}, {this.props.color}
{this.props.showDeleteButton ? <DeleteButton onDelete={this.props.onDelete}/> : null }
</li>
)
}
}
const calculateOffset = date => {
let countdown = new Date(date)
let today = new Date
let timeDiff = countdown.getTime() - today.getTime()
let diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24))
return diffDays
}
class CountdownList extends React.Component {
countdowns() {
let props = this.props
// let onClick = this.props.onClick
// let onDoubleClick = this.props.onDoubleClick
let rows = []
this.props.countdowns.forEach(function(countdown, index) {
rows.push(
<Countdown
key={index}
title={countdown.title}
days={calculateOffset(countdown.date)}
color={countdown.color}
showDeleteButton={countdown.showDeleteButton}
onDelete={() => props.onDelete(index)}
onClick={() => props.onClick(index)}
onDoubleClick={() => props.onDoubleClick(index)}
/>
)
})
return rows
}
render() {
return (
<div>
<ul>
{this.countdowns()}
</ul>
</div>
)
}
}
class InputField extends React.Component {
render() {
return (
<input
type='text'
placeholder={this.props.placeholder}
value={this.props.input}
onChange={this.props.handleInput}
/>
)
}
}
class DatePicker extends React.Component {
render() {
return (
<input
type='date'
value={this.props.date}
onChange={this.props.handleDateInput}
/>
)
}
}
class CountdownForm extends React.Component {
constructor(props) {
super(props)
this.state = {
title: this.props.title || '',
date: this.props.date || '',
color: this.props.color || ''
}
}
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.title || '',
date: nextProps.date || '',
color: nextProps.color || ''
})
}
handleSubmit(e) {
e.preventDefault()
this.props.onSubmit(this.state, this.reset())
}
reset() {
this.setState({
title: '',
date: '',
color: ''
})
}
handleTitleInput(e) {
this.setState({
title: e.target.value
})
}
handleDateInput(e) {
this.setState({
date: e.target.value
})
}
handleColorInput(e) {
this.setState({
color: e.target.value
})
}
render() {
return (
<form
onSubmit={(e) => this.handleSubmit(e)}
>
<h3>Countdown </h3>
<InputField
placeholder='title'
input={this.state.title}
handleInput={(e) => this.handleTitleInput(e)}
/>
<DatePicker
date={this.state.date}
handleDateInput={(e) => this.handleDateInput(e)}
/>
<InputField
placeholder='color'
input={this.state.color}
handleInput={(e) => this.handleColorInput(e)}
/>
<button type='submit'>Submit</button>
</form>
)
}
}
class CountdownApp extends React.Component {
constructor() {
super()
this.state = {
countdowns: [
{title: 'My Birthday', date: '2017-07-25', color: '#cddc39', showDeleteButton: false},
{title: 'Driving Practice', date: '2017-07-29', color: '#8bc34a', showDeleteButton: false},
{title: 'Korean BBQ', date: '2017-08-15', color: '#8bc34a', showDeleteButton: false}
]
}
}
handleCountdownForm(data) {
if (this.state.editId) {
const index = this.state.editId
let countdowns = this.state.countdowns.slice()
countdowns[index] = data
this.setState({
title: '',
date: '',
color: '',
editId: null,
countdowns
})
} else {
data.showDeleteButton = false
const history = this.state.countdowns.slice()
this.setState({
countdowns: history.concat(data),
})
}
}
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList)
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
handleCountdown(index) {
const countdownList = this.state.countdowns.slice()
let countdown = countdownList[index]
countdown.showDeleteButton = !countdown.showDeleteButton
this.setState({
countdowns: countdownList
})
}
handleDblClick(index) {
const countdownList = this.state.countdowns
const countdown = countdownList[index]
this.setState({
title: countdown.title,
date: countdown.date,
color: countdown.color,
editId: index
})
}
render() {
return (
<div>
<CountdownForm
title={this.state.title}
date={this.state.date}
color={this.state.color}
onSubmit={(data) => {this.handleCountdownForm(data)}}
/>
<CountdownList
countdowns={this.state.countdowns}
onDelete={(index) => this.handleDelete(index)}
onClick={(index) => this.handleCountdown(index)}
onDoubleClick={(index) => this.handleDblClick(index)}
/>
</div>
)
}
}
ReactDOM.render(
<CountdownApp />,
document.getElementById('app')
)
I managed to find the answer to my own question!
setState worked as expected. The bug was due to <li> container that wrapped the event handler.
Clicking <li> causes it to call onClick event (which is managed by handleCountdown function in CountdownApp component) which causes it to setState.
As the delete button was wrapped in <li> container, clicking the delete button calls 2 event listeners - handleCountdown and handleDelete. handleCountdown is called twice in this case, once from clicking <li> to expand and the next call when the delete button is clicked.
There is a high chance that the last async setState dispatched from handleCountdown overwrites handleDelete's setState. Hence, the bug.
Here is changes: (I recoded everything again so the names might differ a little but the logic stays the same)
class Countdown extends React.Component {
render () {
return (
<li>
<div onClick={this.props.onClick} > // Add this div wrapper!
{this.props.title} - {this.props.days}, {this.props.color}
</div>
{this.props.toShow ?
<ButtonsGroup
onDelete={this.props.onDelete}
onEdit={this.props.onEdit}
/>
: null}
</li>
)
}
}
So the solution is to separate the clickable area and the buttons. I added a div wrapper over the text in <li> so whenever the text in <li> is clicked, the added <ul> will be out of onClick event handler area.

Resources