Removing an item from a todo list in react - javascript

I"ve been trying my hands on learning react and just hit a snag. Having issue understanding passing props across child components as relates to the problem being encountered. I'm having problems deleting an item from my todo list and can for the life of me figure out wat's wrong.
import React, { Component } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
class App extends Component {
constructor(props){
super(props);
this.state = {
userInput: "",
todoList: []
}
this.onChange= this.onChange.bind(this);
this.addItem=this.addItem.bind(this);
this.handleDelete=this.handleDelete(this);
}
//update input state
onChange=(e)=>{
this.setState({
userInput: e.target.value
})
}
//update todo list
addItem=(e)=>{
e.preventDefault();
if(this.state.userInput!==""){
const userData={
//create a specific user id for each input
id: Math.random(),
value: this.state.userInput
}
const list=[...this.state.todoList];
list.push(userData);
//Reset userInput after inputing data
this.setState({
userInput: "",
todoList: list
})
}
}
//Deleting an item
handleDelete=(id)=>{
const list=[...this.state.todoList];
const updatedList=list.filter(item=>item.id!==id)
this.setState({
todoList:updatedList
})
}
render() {
return(
<div>
<h1>
My Todo List
</h1>
<Form value={this.state.userInput} onChange={this.onChange} onSubmit={this.addItem} />
<Todo list={this.state.todoList} onDelete={this.handleDelete}/>
</div>
)
}
}
export default App;
Above is the parent component
import React, { Component } from 'react';
class Form extends Component {
render() {
return (
<div>
<form >
<input type="text" value={this.props.value} onChange={this.props.onChange}/><button className="btn btn-primary" type="submit" onClick={this.props.onSubmit}>add</button>
</form>
</div>
);
}
}
export default Form;
import React, { Component } from 'react';
class Todo extends Component {
render() {
const todos=this.props.list.map((item,index)=><li key={item.id}>{item.value} <i onClick={this.props.onDelete(item.id)} class="fas fa-trash"></i></li>)
return (
<div>
<ul>
{todos}
</ul>
</div>
);
}
}
export default Todo;

You almost got it right. You need to provide a callback to your onClick handler.
The way you have it now you are providing the onClick handler with the result of running that function. In other words onDelete is being called on each render and onClick is given the return value of onDelete (which is undefined).
You want to give it the function itself. Just change it to:
onClick={() => this.props.onDelete(item.id)}
EDIT: You also made a mistake in your binding of this.handleDelete. It should be:
this.handleDelete = this.handleDelete.bind(this);
but you did:
this.handleDelete = this.handleDelete(this);
which is why you got the error.

Apparently, your Todo component isn't providing the click handler properly:
<i onClick={this.props.onDelete(item.id)} class="fas fa-trash"></i>
should be:
<i onClick={() => this.props.onDelete(item.id)} class="fas fa-trash"></i>
The reason is because onClick expects a function and passing it this way onClick={this.props.onDelete(item.id)} provides the return of the onDelete function.

Related

How do I access and setState() of a parent component, from a child component onClick

I'm a young dev trying to learn some Reactjs, but I'm having trouble understanding how to configure this Todo app. My goal is to have a button that will add items to the list once entered and submitted. I feel like I'm pretty close to having it figured out.
I've got an App component (parent), button component, and a List component(also a header and item component). the list has a variable that has an empty array for me to add items to, which I reference in my App component.
Here lies the problem. I have an event listener on my button that runs a function that sets the state. I'm logging the list every time I click, which shows that the array is receiving the text inputs and making a new object. However, the DOM is not re-rendering what confuses me even more, is that when I make a slight edit (random semicolon) the DOM renders the items that were entered and logged before I last saved, but remains unresponsive.
What am I missing here? Also, I understand that lifecycle methods like componentDidMount() or componentDidUpdate() may be useful, but I do not fully understand how and where to use them.
export class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.setState(() => {
TodoList.push({id: (TodoList.length+1), name: input})
})
console.log(TodoList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button />
</div>
</div>
)
}
}
export default App
In your App.js, you should pass a function to <Button />, this technique called function as prop in react. The App.js code should look like below:
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
addTodo = (todo) => {
this.setState({ todos: [...this.state.todos, todo] })
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button todosList={this.state.todos} addTodo={(todo) => this.addTodo(todo)} />
</div>
</div>
)
}
In the code for Button.js, you get this function via this.props
export default class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.props.addTodo({id: this.props.todosList.length + 1, name: input })
console.log(this.props.todosList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}

React this.state.addroom.map is not a function

So I am trying to gen a div with a button onClick of a button but I get an error that is stopping me from doing this.
Error: TypeError: this.state.addroom.map is not a function
But I saw that when I click my button once it doesn't show the error but it doesn't generate the div with the button either.
Here is my code:
import React, { Component } from 'react';
import Select, { components } from 'react-select';
import styles from '../styles/loginsignup.css'
import axios from 'axios'
import nextId from "react-id-generator";
export default class AccomodationInfo extends Component {
constructor() {
super();
this.state = {
accomcate: null,
addroom: ['one'],
isLoading: true,
}
}
handleClick = event => {
const htmlId = nextId()
event.preventDefault()
const addroom = this.state.addroom
this.setState({ addroom: htmlId })
return (
<div>
{this.state.addroom.map(addrooms => (
<button key= {addroom.id} className={addrooms.modifier}>
{addrooms.context}
</button>
))}
</div>
);
}
render() {
return(
<div>
<button onClick={this.handleClick}>Add</button>
</div>
)
}
}
}
Anyone knows what causes it and how we can fix it?
There are a few things off with your code.
First of all the addroom in your state is a string array in your constructor, but in the handleClick method you set it like this.setState({ addroom: htmlId }) which will set it to a string and on a string type the map function is not defined, hence the error.
You should add an item to the array like this.setState({ addroom: [...this.state.addroom, htmlId] })
Secondly, in your handleClick you shouldn't return jsx, if you wan to render data for your addroom array, you should do it in the render method, and in the handleClick you should just modify the addroom state variable.
You can achieve this like:
render() {
return (
<div>
<button onClick={this.handleClick}>Add</button>
{this.state.addroom.map((addroom) => (
<button>{addroom}</button>
))}
</div>
)
}
Lastly, your addrom variable is a string array only, so you can't access id, modifier and context in an item in that array.

React Component not updating with state change

I currently have a reducer that does a deep copy of state and returns it with the updated value.
function countableItems(state = initialState, action) {
switch (action.type) {
case types.ADD_TO_SUM:
let denomMap = findDenomination(state.denomGroups, action),
nestedCopy = Immutable.fromJS(state);
return nestedCopy.setIn(['denomGroups', denomMap.group, denomMap.key, denomMap.index, 'sum'], parseFloat(action.value)).toJS();
default:
return state;
}
}
In my render function of the display Component I see the correct updated values in this.props.denoms The render() function builds up child <DenomInput> components, and when I set my breakpoints I see the correct data being passed in
render() {
let denomGroups = this.props.denoms.map((denom, i) => {
return (
Object.keys(denom).map((key) => {
let denoms = denom[key].map((item, i) => {
return <DenomInput denom={item} onDenomChange={this.onDenomChange} key={i}></DenomInput>
});
return (<div className="col"><h2>{key}</h2>{denoms}</div>)
})
);
});
return (
<div className="countable-item-wrapper">
<div className="row">
{denomGroups}
</div>
</div>
);
}
However when the <DenomInput> components render it renders the same value as what they were initially set
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class DenomInput extends Component {
constructor(props) {
super(props);
this.state = { denom: props.denom }
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp = (e) => {
this.props.onDenomChange(e.target.value, this.state.denom.name);
}
render() {
return (
<div className="input-group denom">
<span className="input-group-addon">{this.state.denom.label}</span>
<input
type="text"
className="form-control"
onChange={this.handleKeyUp}
value={this.state.denom.sum} />
<span className="input-group-addon">{this.state.denom.count | 0}</span>
</div>
);
}
}
DenomInput.PropTypes = {
denom: PropTypes.object.isRequired,
onDenomChange: PropTypes.function
}
export default DenomInput;
What piece am I missing to update the view with React and Redux?
May be componentWillReceiveProps can do the trick. It will update the state of the component whenever new data is receive from parent, and call the render function again.
Try
class DenomInput extends Component {
...
componentWillReceiveProps(nextProps) {
this.setState({ denom: nextProps.denom })
}
...
}
It looks like you're seeding your initial state with the props from your store. You then render from the component state, but you never update the component state. They only get set once because constructor is only called once the component is rendered. To fix, either remove this component state entirely and just connect it to the redux store, or update the component state onChange. I recommend removing the local state. I have found that keeping the two states in sync is error-prone.
constructor(props) {
super(props);
this.state = { denom: props.denom }
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp = (e) => {
this.props.onDenomChange(e.target.value, this.state.denom.name);
this.setState({ denom: /*new state identitcal to change in redux store*/ })
}
edit2: An example of raising state up. The steps are:
1. Connect one of your parent components and grab the appropriate slice of state with a mapStateToProps function.
2. Pass the props through your connected parent component to DenomInput.
4. In this.denomsChange, dispatch the appropriate action. It is unclear what this is since you did not include your action in the post.
class DenomInput extends Component {
...
render() {
return (
<div className="input-group denom">
<span className="input-group-addon">{this.props.denom.label}</span>
<input
type="text"
className="form-control"
onChange={this.handleKeyUp}
value={this.props.denom.sum} />
<span className="input-group-addon">{this.props.denom.count | 0}</span>
</div>
);
}
}
export default DenomInput;

React - Can A Child Component Send Value Back To Parent Form

The InputField & Button are custom components that go into a form to create a form. My issue is how do I send the data back up to form so that on button click, I can fire ajax on the form with data (username & password):
export default auth.authApi(
class SignUpViaEmail extends Component{
constructor(props){
super(props);
this.state = {
email : "",
password : ""
};
this.storeEmail = this.storeEmail.bind( this );
this.storePassword = this.storePassword.bind( this );
}
storeEmail(e){
this.setState({ email : e.target.value });
}
storePassword(e){
this.setState({ password : e.target.value });
}
handleSignUp(){
this.props.handleSignUp(this.state);
}
render(){
return(
<div className="pageContainer">
<form action="" method="post">
<InputField labelClass = "label"
labelText = "Username"
inputId = "signUp_username"
inputType = "email"
inputPlaceholder = "registered email"
inputClass = "input" />
<Button btnClass = "btnClass"
btnLabel = "Submit"
onClickEvent = { handleSignUp } />
</form>
</div>
);
}
}
);
Or Is it not recommended & I should not create custom child components within the form?
child component => InputField
import React,
{ Component } from "react";
export class InputField extends Component{
constructor( props ){
super( props );
this.state = {
value : ""
};
this.onUserInput = this.onUserInput.bind( this );
}
onUserInput( e ){
this.setState({ value : e.target.value });
this.props.storeInParentState({[ this.props.inputType ] : e.target.value });
}
render(){
return <div className = "">
<label htmlFor = {this.props.inputId}
className = {this.props.labelClass}>
{this.props.labelText}
</label>
<input id = {this.props.inputId}
type = {this.props.inputType}
onChange = {this.onUserInput} />
<span className = {this.props.validationClass}>
{ this.props.validationNotice }
</span>
</div>;
}
}
Error : I get the error e.target is undefined on the parent storeEmail func.
React's one-way data-binding model means that child components cannot send back values to parent components unless explicitly allowed to do so. The React way of doing this is to pass down a callback to the child component (see Facebook's "Forms" guide).
class Parent extends Component {
constructor() {
this.state = {
value: ''
};
}
//...
handleChangeValue = event => this.setState({value: event.target.value});
//...
render() {
return (
<Child
value={this.state.value}
onChangeValue={this.handleChangeValue}
/>
);
}
}
class Child extends Component {
//...
render() {
return (
<input
type="text"
value={this.props.value}
onChange={this.props.onChangeValue}
/>
);
}
}
Take note that the parent component handles the state, while the child component only handles displaying. Facebook's "Lifting State Up" guide is a good resource for learning how to do this.
This way, all data lives within the parent component (in state), and child components are only given a way to update that data (callbacks passed down as props). Now your problem is resolved: your parent component has access to all the data it needs (since the data is stored in state), but your child components are in charge of binding the data to their own individual elements, such as <input> tags.
Addendum
In response to this comment:
What if we render a list of the child component? Using this single source of truth in Lifting state up technique will let the parent controls all the state of all the child inputs right? So how can we access each of the value input in the child component to (which is rendered as list) from the parent component?
For this case, you may map a child component for each element in the list. For example:
class Parent extends Component {
//...
handleChangeListValue = index => event => {
this.setState({
list: this.state.list
.map((element, i) => i === index ? event.target.value : element)
});
}
//...
render() {
return this.state.list.map((element, i) => (
<Child
value={element}
onChangeValue={this.handleChangeListValue(i)}
/>
));
P.S. Disclaimer: above code examples are only for illustrative purposes of the concept in question (Lifting State Up), and reflect the state of React code at the time of answering. Other questions about the code such as immutable vs mutable array updates, static vs dynamically generated functions, stateful vs pure components, and class-based vs hooks-based stateful components are better off asked as a separate question altogether.
React class component
Parent.js
import React, { Component } from 'react';
import Child from './child'
class Parent extends Component {
state = {
value: ''
}
onChangeValueHandler = (val) => {
this.setState({ value: val.target.value })
}
render() {
const { value } = this.state;
return (
<div>
<p> the value is : {value} </p>
<Child value={value} onChangeValue={this.onChangeValueHandler} />
</div>
);
}
}
export default Parent;
Child.js
import React, { Component } from 'react';
class Child extends Component {
render() {
const { value , onChangeValue } = this.props;
return (
<div>
<input type="text" value={value} onChange={onChangeValue}/>
</div>
);
}
}
export default Child;
React hooks
Parent.js
import { useState } from "react";
import Child from "./child";
export default function Parent() {
const [value, changeValue] = useState("");
return (
<div>
<h1>{value}</h1>
<Child inputValue={value} onInputValueChange={changeValue} />
</div>
);
}
Child.js
export default function Child(props) {
return (
<div>
<input
type="text"
value={props.inputValue}
onChange={(e) => props.onInputValueChange(e.target.value)}/>
</div>
);
}
Parent.js
import SearchBar from "./components/SearchBar";
function App() {
const handleSubmit = (term) => {
//Log user input
console.log(term);
};
return (
<div>
<SearchBar onPressingEnter={handleSubmit} />
</div>
);
}
export default App;
Child.js
import { useState } from "react";
function SearchBar({ onPressingEnter }) {
const [UserSearch, setname] = useState("[]");
/* The handleChange() function to set a new state for input */
const handleChange = (e) => {
setname(e.target.value);
};
const onHandleSubmit = (event) => {
//prevent form from making a http request
event.preventDefault();
onPressingEnter(UserSearch);
};
return (
<div>
<form onSubmit={onHandleSubmit}>
<input
type="search"
id="mySearch"
value={UserSearch}
onChange={handleChange}
name="q"
placeholder="Search the site…"
required
/>
</form>
</div>
);
}
export default SearchBar;
You can add a "ref name" in your InputField so you can call some function from it, like:
<InputField
ref="userInput"
labelClass = "label"
labelText = "Username"
inputId = "signUp_username"
inputType = "email"
inputPlaceholder = "registered email"
inputClass = "input" />
So you can access it using refs:
this.refs.userInput.getUsernamePassword();
Where getUsernamePassword function would be inside the InputField component, and with the return you can set the state and call your props.handleSignUp

React component could not sync props which created by dynamic ReactDOM.render

When I use React+Redux+Immutable, I get an issue: the component created by dynamic way, when the props change, component not rerender.
Is it React bug?
I deleted business code, just React code here: http://codepen.io/anon/pen/GoMOEZ
or below:
import React from 'react'
import ReactDOM from 'react-dom'
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'tom'
}
}
dynamic() {
ReactDOM.render(<B name={this.state.name} changeName={this.changeName.bind(this)} type={false}/>, document.getElementById('box'))
}
changeName() {
this.setState({
name: 'tom->' + Date.now()
});
}
render() {
return <div>
top name: {this.state.name}
<B name={this.state.name} changeName={this.changeName.bind(this)} type={true}/>
<div id="box"></div>
<button onClick={this.dynamic.bind(this)}>dynamic add component</button>
</div>
}
}
class B extends React.Component {
render() {
return <div>
{this.props.type ? '(A)as sub component' : '(B)create by ReactDOM.render'}
- name:【{this.props.name}】
<button onClick={this.props.changeName}>change name</button>
</div>
}
}
ReactDOM.render(
<A/>,
document.getElementById('example')
);
It is not a bug, it is just not React-way to do what you want. Each call to A.render will overwrite <div id="box">...</div> deleting elements added by A.dynamic.
More idiomatic way is to add some flag, set it in onClick handler and use it in A.render to decide if <div id="box"> should be empty or not.
See edited code on codepen: http://codepen.io/anon/pen/obGodN
Relevant parts are here:
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'tom',
showB: false // new flag
}
}
changeName() {
this.setState({
name: 'tom->' + Date.now()
});
}
// changing flag on button click
showB() {
this.setState({showB: true})
}
render() {
// `dynamic` will contain component B after button is clicked
var dynamic;
if(this.state.showB) {
dynamic = <B
name = {this.state.name}
changeName = {this.changeName.bind(this)}
type = {false} />
}
return <div>
top name: {this.state.name}
<B name = {this.state.name}
changeName = {this.changeName.bind(this)}
type = {true}/>
<div>{dynamic}</div>
<button onClick = {this.showB.bind(this)}>
dynamic add component
</button>
</div>
}
}
Update
You can still use your approach, but you need to manually update dynamically created component.
E.g. you can manually rerender it in changeName function.
changeName() {
this.setState({
name: 'tom->' + Date.now()
}, this.dynamic.bind(this));
}
Note, that this.dynamic is passed as a second argument to this.setState this ensures that it will be called when state is really updated. Just adding this.dynamic() after this.setState({...}) will use not-yet-updated state.
Codepen here: http://codepen.io/anon/pen/EPwovV

Categories

Resources