Filtering an array in React - javascript

I am making a To-Do Application in React. I got stuck at some point.
I'm mapping through items in an array, and displaying it in an unordered list. I'm trying to use the filter function, to remove the deleted items from the array.
I assume that the problem in my code can be somewhere there, that I am passing the event object but pointing to the button, instead of the list item.
How can I do it in React? You can find my code attached below. Also it would be great to clear the input field after submitting an item.
import React, { Component } from 'react';
class ToDoList extends Component {
constructor(props) {
super(props);
this.state = {list: [], items: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleChange(event) {
this.setState({items: event.target.value})
console.log(event.target.value);
}
handleSubmit(event) {
this.setState({ list: [...this.state.list, this.state.items]})
event.preventDefault();
}
handleRemove(event) {
const filteredArray = this.state.list.filter(item => item !== event.target.value)
this.setState({list: filteredArray});
console.log(event.target);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
<input
type="text"
value={this.state.value}
onChange={this.handleChange} />
</label>
<input
onClick={this.handleSubmit}
type="submit"
value="Submit" />
</form>
<ul>
{this.state.list.map((i, index) => (
<li key={index+1}>
{i}
<button
onClick={this.handleRemove}>
X
</button>
</li>
))}
</ul>
<p>Remaining: {this.state.list.length}</p>
</div>
);
}
}
export default ToDoList;

I would recommend using the optional additional arguments to .bind in order to change what's passed to your handler.
Call .bind(this. index) within your map method to pass the index of the element to be removed to the handler:
<ul>
{this.state.list.map((i, index) => (
<li key={index+1}>{i}<button onClick={this.handleRemove.bind(this, index)}>X</button></li>
))}
</ul>
And then update the handler to just remove the specified element:
handleRemove(index) {
const filteredArray = this.state.list.filter((_, i) => i !== index);
this.setState({
list: filteredArray
});
}
As to your second question, you should first fix up your input so its value is actually controlled by the state by changing value={this.state.value} to value={this.state.items}:
<input type="text" value={this.state.items} onChange={this.handleChange} />
And then simply clear this.state.items upon submission:
handleSubmit(event) {
this.setState({
list: [...this.state.list, this.state.items],
items: ''
})
event.preventDefault();
}

Check at this line:
<li key={index+1}>{i}<button onClick={this.handleRemove}>X</button></li>
Inside handleRemove, the instruction event.target point to the button. A button doesn't have a value attribute. So, you need to go up to parent and get it's content:
// childNodes[0] is a TextNode, so, to get it's content use data pop
const toRemove = event.target.parentNode.childNodes[0].data.trim();
const filteredArray = this.state.list.filter(item => item !== toRemove);
Another option is wrap the item in a element, like span:
<li key={index+1}><span>{i}</span><button onClick={this.handleRemove}>X</button></li>
And on remove get it to get the item value:
const toRemove = event.target.parentNode.children[0].textContent.trim();
const filteredArray = this.state.list.filter(item => item !==
event.target.value)

Related

Cannot delete Item from Todo-list in React

I have created a simple Todo list, adding item works but when I clicked on the 'delete' button, my Item is not deleting any item from the List. I would like to know what mistakes I am making in my code, Would appreciate all the help I could get. Thanks in Advance!
And ofcourse, I have tried Looking through google and Youtube, But just couldnot find the answer I am looking for.
Link: https://codesandbox.io/embed/simple-todolist-react-2019oct-edbjf
App.js:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import TodoForm from "./TodoForm";
import Title from "./Title";
class App extends React.Component {
// myRef = React.createRef();
render() {
return (
<div className="App">
<Title />
<TodoForm />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
----------------------
TodoForm.js:
import React from "react";
import ListItems from "./ListItems";
class TodoForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
id: 0
};
}
inputValue = e => {
this.setState({ value: e.target.value });
};
onSubmit = e => {
e.preventDefault();
this.setState({
value: "",
id: 0,
items: [...this.state.items, this.state.value]
});
};
deleteItem = (itemTobeDeleted, index) => {
console.log("itemTobeDeleted:", itemTobeDeleted);
const filteredItem = this.state.items.filter(item => {
return item !== itemTobeDeleted;
});
this.setState({
items: filteredItem
});
};
// remove = () => {
// console.log("removed me");
// };
render() {
// console.log(this.deleteItem);
console.log(this.state.items);
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="Enter task"
value={this.state.value}
onChange={this.inputValue}
/>
<button>Add Item</button>
</form>
<ListItems items={this.state.items} delete={() => this.deleteItem} />
</div>
);
}
}
export default TodoForm;
----------------------
ListItems.js
import React from "react";
const ListItems = props => (
<div>
<ul>
{props.items.map((item, index) => {
return (
<li key={index}>
{" "}
{item}
<button onClick={props.delete(item)}>Delete</button>
</li>
);
})}
</ul>
</div>
);
export default ListItems;
The problem is, you must pass a function to the onDelete, but you are directly calling the function
updating the delete item like so,
deleteItem = (itemTobeDeleted, index) => (event) => {
and update this line, (since the itemTobeDeleted was not reaching back to the method)
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
fixes the issue
Working sandbox : https://codesandbox.io/s/simple-todolist-react-2019oct-zt5w6
Here is the working example: https://codesandbox.io/s/simple-todolist-react-2019oct-xv3b5
You have to pass in the function into ListItems and in ListItems run it passing in the correct argument (the item).
Your solution is close; there are two fixes needed for your app to work as expected.
First, when rendering the ListItems component, ensure that the item is passed through to your deleteItem() function:
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
Next, your ListItems component needs to be updated so that the delete callback prop is called after an onclick is invoked by a user (rather than immediatly during rendering of that component). This can be fixed by doing the following:
{ props.items.map((item, index) => {
return (<li key={index}>{item}
{/*
onClick is specified via inline callback arrow function, and
current item is passed to the delete callback prop
*/}
<button onClick={() => props.delete(item)}>Delete</button>
</li>);
)}
Here's a working version of your code sandbox
first make a delete function pass it a ind parameter and then use filter method on your array in which you saved the added values like
function delete(ind){
return array.filter((i)=>{
return i!==ind;
})
}
by doing this elements without the key which you tried to delete will not be returned and other elements will be returned.

How to prepend a list Item in react correctly

i have trouble to prepend a dynamically generated list item correctly
to show what i mean i setup this jsfiddle
add new item (type in 1)
add new value (type in a)
add new item (type in 2)
you will see that the value stays on top and is now inside item 2.
how do i fix this behavior so that the second added item has empty value and the first item keeps his value?
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
items: []
}
}
addItem = (e) => {
this.setState({
items: [e.target.value, ...this.state.items]
})
}
render() {
return (
<div>
<p>Add new Item</p>
<input type="text" onChange={this.addItem}></input>
<ul>
{this.state.items.map(item => (
<li key={item.id}>
<h4>{item}</h4>
<p>add New Value</p>
<input type="text"></input>
</li>
))}
</ul>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
The problem is here:
{this.state.items.map(item => (
<li key={item.id}>
...
The key prop is used by React as a reference to each item in a list, so it should be unique (and stable) for each item. Your item doesn't have an id property, it's just a string value from the input, so item.id is undefined. You could use item itself (the input value) as the key, as long as you don't expect any duplicate item names.
To make this more robust, you can store your items as objects with value and id properties, and generate a unique ID for each item as it's created. This is one simple way of doing that:
class TodoApp extends Component {
state = {
items: []
}
lastID = 0;
addItem = (e) => {
this.setState({
items: [{ value: e.target.value, id: this.lastID++}, ...this.state.items]
})
}
render() {
...
{this.state.items.map(item => (
<li key={item.id}>
<h4>{item.value}</h4>
...
}
}
Note that we're mutating lastID, but in this case that's OK since it's a class property and not stored in state (it won't cause a re-render when it changes).

How to add values of dyanamically added fields

I've tried to add new fields here, each time button '+' is clicked it adds field input. It is working fine but the problem I've stuck upon is I want to add the value of those input fields. As I am new to react I am not finding a way to achieve it. Is it possible to achieve this.
class InputFields extends React.Component{
render(){
return (
<div>
<input name={`value[${this.props.index + 1}]`} onChange={this.onChangeValue} />
</div>
);
}
}
class Main extends React.Component{
constructor(props){
super(props);
this.values = props.value;
this.state={
inputs:[],
values:[]
};
this.addInputs = this.addInputs.bind(this);
}
addInputs() {
const inputs = this.state.inputs.concat(InputFields);
this.setState({ inputs });
}
onChangeValue(e){
var value = e.target.value;
this.value =value;
this.addValues();
}
addValues(){
...
}
render () {
const inputs = this.state.inputs.map((Element, index) => {
return <Element key={ index } index={ index } />
});
return <div>
<div>
<input name={`value[${this.props.index}]`} onChange={this.onChangeValue} />
</div>
<div>
<button className="btn btn-sm btn-primary" onClick={this.addInputs}>+</button>
</div>
</div>
}
}
ReactDOM.render(
<Main />,
document.getElementById('calculator')
);
It't not required to manage two different variables to manage it, you can loop it through the array and can change the value on onChange event
import React from "react";
export default class Example extends React.Component {
state = {
values: [null]
};
add() {
this.setState(prevState => prevState.values.push(null));
}
changeVal(val, index) {
this.setState(prevState => (prevState.values[index] = parseFloat(val)));
}
getSum() {
let sum = 0;
for (let i = 0; i < this.state.values.length; i++) {
if (this.state.values[i] !== null) {
sum += this.state.values[i];
}
}
return sum;
}
render() {
return (
<div>
{this.state.values.map((val, index) => (
<div key={index}>
<input
onChange={e => this.changeVal(e.target.value, index)}
type="number"
value={val}
placeholder="Enter a value"
/>
</div>
))}
<hr />
Sum is {this.getSum()}
<hr />
<button onClick={this.add.bind(this)}> +Add</button>
</div>
);
}
}
Edit
Added the method getSum() and converting the number to float in changeVal() method
Codesandbox link - https://codesandbox.io/s/235o6xoxyr
In react, the view is a function of data: UI = fn(data)
This means that you'll write a component tree that can transform data to view.
And only manipulate data, unlike what you might be familiar with in jQuery for example.
In practice, this would mean:
let a component Input handle the rendering of one input:
const Input = ({value, onChange}) =>
<input value={value} onChange={event => event.target.value} />
in your state, only save the list of input values as strings. React will take care of transforming each value to an Input component later on
this.state = {inputs: []}
write a function responsible of adding new inputs, to the state and not the view
addInput = () => {
this.setState({
inputs: this.state.inputs.concat('')
})
}
create a method that takes care of changing the value of one input in the list
changeValue = (i, newValue) => {
this.setState({
inputs: [
...this.state.inputs.slice(0, i),
newValue,
...this.state.inputs.slice(i+1)]
})
}
in the render method of Main, loop over your list of inputs and transform to a list of Input components
this.state.inputs.map(
(input, i) =>
<Input
key={i}
value={input}
onChange={newValue => this.changeValue(i, newValue)}
/>
)

Error dynamically updating an array list in ReactJs

I am learning reactJS and so I am trying my hands on an example. This example has a form textfield that can add an item to an existing array on click of a button. I am having errors here as when I enter a text and click on the button, the array list is not updated except I try to make changes to the text entered in the textfield. This is what I am doing:
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
currentName : '',
arrays : ['john', 'james', 'timothy']
}
}
render() {
const showNames = this.state.arrays.map((thisName) => {
const values = <li>{thisName}</li>;
return values;
});
const getText = (e) => {
let value = e.target.value;
this.setState({
currentName : value
})
}
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" onChange={getText}/>
<button type="button" onClick={addToUsers}>Add User</button>
</form>
<ul>
{showNames}
</ul>
</div>
);
}
}
export default App;
There are a host of things wrong with this, but your issue is likely that you need to use setState to modify state.
import React, { Component } from 'react';
class App extends Component {
constructor(){
super();
this.state = {
names: ['john', 'james', 'timothy']
}
}
addToUsers = () => {
this.setState(
prevState => ({
names: [...prevState.names, this.input.value]
})
)
}
render() {
const names = this.state.names.map(
(name, index) => <li key={index}>{name}</li>
)
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" ref={e => this.input = e} />
<button type="button" onClick={this.addToUsers}>Add User</button>
</form>
<ul>
{names}
</ul>
</div>
)
}
}
export default App;
This quick edit changes a few things:
Uses setState for the addToUsers method
Eliminate onChange tracking and pull the name directly from the input when the button is clicked
Move the addToUsers method out to the component class rather than defining it on render
Rename this.state.arrays to this.state.names
Simplify conversion of this.state.names into list items
Set key on array elements (name list items)
Use prevState in setState to avoid race conditions
You need to make sure you update state using the setState method.
When you update arrays you are reaching into the state object and manipulating the data directly instead of using the method.
Instead try something like:
const addToUsers = () => {
const newArray = this.state.arrays.concat([this.state.currentName]);
this.setState({
arrays: newArray
});
}
You probably must add
onChange={getText}.bind(this)
to your functions.
Also change this
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
to this
const addToUsers = () => {
this.setState({here put your variable})
}

React - Update Value Of Input After Submit

I'm trying to make a feature where a user can edit a submitted value. So to be completely clear:
You would enter some text
Click submit and that value will be pushed into an array
You will be able to see your value on the dom
If you made an error, you can click on that input and change the value, also updating the state of that value in the already pushed array.
On a button click, you will update the state and have a newly edited value.
I'm stuck on the part of changing the state of the value of the pushed items in the array.
For example:
If I were to click on the field of 'Bob', edit it and click submit, the value of whatever I changed it to would also change the state of what was originally in my array to the new value.
This is what I have so far:
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
notes: ['hello', 'bob'],
val: ''
}
}
submit = () => {
const { notes, val } = this.state
notes.push(val)
this.setState({notes})
}
handleEdit = e => {
console.log(e)
}
render() {
return (
<div>
<input
type="text"
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={this.submit}>Submit</button>
{this.state.notes.map(item => {
return (
<form onSubmit={e => e.preventDefault()}>
<input
type="text"
defaultValue={item}
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={() => this.handleEdit(item)}>Submit
Change</button>
</form>
)
})}
</div>
)
}
}
Try this kind of thing :
handleEdit = (item) => {
const notes = this.state.notes.slice();
const index = notes.indexOf(item);
notes[index] = this.state.val;
this.setState({
notes
})
}

Categories

Resources