I'm practicing React and I'm making a TodoList Component. But currently, I can add a todo item that is empty. And I want a message saying that it's not allowed.
My issue is even if the field is empty, I can create a new item.
Here is my code:
import React, { Component } from "react";
class TodoList extends Component {
constructor() {
super();
this.state = {
userInput: '',
items: []
};
}
onChange(event) {
this.setState({
userInput: event.target.value
}, () => console.log(this.state.userInput));
}
addTodo(event) {
event.preventDefault();
this.checkField();
this.setState({
userInput: '',
items: [...this.state.items, this.state.userInput] },
() => console.log(this.state.items));
}
deleteTodo(item) {
const array = this.state.items;
const index = array.indexOf(item);
array.splice(index, 1);
this.setState({
items: array
})
}
checkField() {
if(this.state.userInput.length === 0) {
let emptyMessageDom = document.createElement("p");
document.body.appendChild(emptyMessageDom);
emptyMessageDom.innerHTML ="This is empty!!"
}
}
renderTodos() {
return this.state.items.map((item, index) => {
return (
<div key={index}>
{item} {index} | <button onClick={this.deleteTodo.bind(this, item)}>X</button>
</div>)
})
}
render() {
return(
<div>
<form>
<input
value={this.state.userInput}
type="text"
placeholder="New item"
onChange={this.onChange.bind(this)}
required
/>
<button onClick={this.addTodo.bind(this)}>Add</button>
</form>
<div>
{this.renderTodos()}
</div>
</div>
);
}
}
export default TodoList;
I tried to put the code of the function checkField() into the setState of addTodo() function, but it doesn't work.
Thanks in advance!
You should use state to show an error message. This will help clean up the way you render and remove the message. Heres a full working example
Update addTodo to conditionally add based on your criteria.
addTodo(event) {
event.preventDefault();
if (!this.checkField()) {
return
}
this.setState({
userInput: '',
items: [...this.state.items, this.state.userInput]
})
}
and then update checkField to validate and return a boolean
checkField() {
if(this.state.userInput.length === 0) {
this.setState({error: 'Field is required.'})
return false;
}
return true;
}
you can then update the render portion to show the error message
<form>
<input
value={this.state.userInput}
type="text"
placeholder="New item"
onChange={this.onChange.bind(this)}
required
/>
{!!this.state.error && <label>{this.state.error}</label>}
<button onClick={this.addTodo.bind(this)}>Add</button>
</form>
Then finally don't forget to remove the error when a change event happens on the input as the validation is now stale.
onChange(event) {
this.setState(
{
userInput: event.target.value,
error: ''
}
);
}
you can modify checkField ,addToDoas follows
checkField() {
if(this.state.userInput.length === 0) {
let emptyMessageDom = document.createElement("p");
document.body.appendChild(emptyMessageDom);
emptyMessageDom.innerHTML ="This is empty!!"
//invalid input to be added as to do item
return false;
}
//valid input to be added as to do item
return true
}
addTodo(event) {
event.preventDefault();
if(this.checkField())
{
this.setState({
userInput: '',
items: [...this.state.items, this.state.userInput] },
() => console.log(this.state.items));
}
}
Related
I'd like to store todo data with localStorage so that it won't disappear after refreshing the page.
I used React class component when started creating.
I've added 'handleFormSubmit' and 'ComponentDidMount' methods.
nothing stores in localStorage when I type todo and choose date.
get an error in ComponentDidMount with
Line 'const result = localData ? JSON.parse(localData) : [];'
:SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
how can I set and get items?
It would be really appreciated if I could get help.
I'd like to make this app really work.
import React from "react"
import TodoItem from "./components/TodoItem"
import todosData from "./components/todosData"
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: todosData,
//setTodos: todosData,
newItem: "",
deadline: "",
editing: false
}
this.handleChange = this.handleChange.bind(this)
this.addTodo = this.addTodo.bind(this)
this.updateInput = this.updateInput.bind(this)
this.deleteItem = this.deleteItem.bind(this)
this.updateItem = this.updateItem.bind(this)
this.updateDeadline = this.updateDeadline.bind(this)
this.updateInputDeadline = this.updateInputDeadline.bind(this)
this.editItem = this.editItem.bind(this)
this.handleFormSubmit = this.handleFormSubmit.bind(this)
}
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
} else {
return todo;
}
});
return { todos: updatedTodos };
});
}
addTodo(e) {
e.preventDefault();
const newTodo = {
id: this.state.todos.length + 1,
text: this.state.newItem,
completed: false,
deadline: this.state.deadline
}
const newTodos = this.state.todos.concat([newTodo]);
this.setState({
todos: newTodos
})
}
updateInput(value, id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
if(todo.id === id) {
return {...todo, text: value}
}else {
return todo;
}
})
return {todos: updatedTodos}
})
}
updateInputDeadline(value, id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
if(todo.id === id) {
console.log(value, id);
return {...todo, deadline: value}
}else {
return todo;
}
})
return {todos: updatedTodos}
})
}
updateItem(e) {
this.setState({
newItem: e.target.value
})
}
updateDeadline(e) {
this.setState({
deadline: e.target.value
})
}
deleteItem(id){
const filteredItems= this.state.todos.filter(item =>
item.id!==id);
this.setState({
todos: filteredItems
})
}
editItem(id) {
this.setState({
editing: id
})
}
handleFormSubmit() {
const { todo, deadline } = this.state;
localStorage.setItem('todo', JSON.stringify(todo));
localStorage.setItem('deadline', deadline);
};
componentDidMount() {
const localData = localStorage.getItem('todo');
const result = localData ? JSON.parse(localData) : [];
const deadlineData = localStorage.getItem('deadline');
this.setState({ result, deadlineData });
}
render() {
const todoItems = this.state.todos.map
(item =>
<TodoItem
key={item.id}
item={item}
handleChange={this.handleChange}
addTodo={this.addTodo}
deleteItem={this.deleteItem}
updateInput={this.updateInput}
updateInputDeadline={this.updateInputDeadline}
isEdited={this.state.editing === item.id}
editItem={this.editItem}
/>)
return (
<div className="todo-list">
<Timer />
<form onSubmit={this.handleFormSubmit}>
<div className="add-todo">
<label>Add an item...</label>
<input
type="text"
name="todo"
placeholder="Type item here..."
value={this.state.newItem}
onChange={this.updateItem}
/>
</div>
<div className="date">
<label htmlFor="deadline">Deadline</label>
<input
type="date" id="start" name="deadline"
min="2021-01-01"
max="2024-12-31"
value={this.state.deadline}
onChange={this.updateDeadline}
/>
</div>
<button type="submit" onClick={this.addTodo}>Add to the list</button>
</form>
{todoItems.length === 0 ? <p>No items</p> : null}
<div className="todoitems">
{todoItems}
</div>
</div>
)
}
}
export default App
When you press the button, there are two events that you are trying to call - addTodo and handleFormSubmit. Since you are calling e.preventDefault() in addTodo, the submit event is never called. You could do all of the actions you need in one of the methods.
My guess is that you are either trying to JSON.parse an array instead of an object, or the value of todo is undefined. You are trying to get todo out of this.state, but you only have todos in your state, so it might be a typo. The same goes for deadline.
You are doing the setting and getting correctly. You could actually get data from localStorage even when you are first setting the state in constructor. But the componendDidMount approach you tried is also good.
constructor(props) {
super(props)
const cachedTodos = localStorage.getItem("todo")
this.state = {
todos: cachedTodos ?? todosData,
...
}
I'm not sure what I'm doing wrong, but I have an input field for entering a search term and trying to filter results based on the search term. The problem is that the first value being passed is an empty string and input is offset by 1 item for each keypress after that. For example, if I type 'sea', it would update the search term to be ' se'. Then, when I try to delete the value, it is offset the other direction, so deleting ' se' ends with 's', which can't be deleted.
(Here's a link to the app in progress: https://vibrant-yonath-715bf2.netlify.com/allpokemon. The full search functionality isn't working quite yet. I'm pretty new at this.)
import React, { Component } from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props) {
super(props);
this.state = {
pokemonList: [],
searchTerm: '',
fetched: false,
loading: false
};
this.updateResults = this.updateResults.bind(this);
}
componentWillMount() {
this.setState({
loading: true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(response => {
this.setState({
pokemonList: response.results,
loading: true,
fetched: true
});
});
}
handleSearchTermChange = (
event: SyntheticKeyboardEvent & { target: HTMLInputElement }
) => {
this.setState({ searchTerm: event.target.value });
this.updateResults();
};
updateResults() {
const filteredList = this.state.pokemonList.filter(
pokemon =>
pokemon.name.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
);
console.log(this.state.searchTerm);
this.setState({
pokemonList: filteredList
});
}
render() {
const { fetched, loading, pokemonList } = this.state;
let content;
if (fetched) {
content = (
<div className="flex-grid">
{pokemonList.map((pokemon, index) => (
<Pokemon key={pokemon.name} id={index + 1} pokemon={pokemon} />
))}
</div>
);
} else if (loading && !fetched) {
content = <p> Loading ...</p>;
} else {
content = <div />;
}
return (
<div>
<input
onChange={this.handleSearchTermChange}
value={this.state.searchTerm}
type="text"
placeholder="Search"
/>
{content}
</div>
);
}
}
export default PokemonList;
setState is asynchronous, so your this.state.searchTerm is not updated when you call updateResults. You could e.g. filter the array in render instead.
Example
class App extends Component {
state = {
pokemonList: [
{ name: "pikachu" },
{ name: "bulbasaur" },
{ name: "squirtle" }
],
searchTerm: ""
};
changeSearchTerm = event => {
this.setState({ searchTerm: event.target.value });
};
render() {
const { pokemonList, searchTerm } = this.state;
const filteredList = pokemonList.filter(pokemon =>
pokemon.name.toUpperCase().includes(searchTerm.toUpperCase())
);
return (
<div>
<input value={searchTerm} onChange={this.changeSearchTerm} />
{filteredList.map(pokemon => <div>{pokemon.name}</div>)}
</div>
);
}
}
I think the problem is that you call this.updateResults();
and then calling this.setState({ searchTerm: event.target.value }); instead of using the callback function for setState.
For example:
this.setState({ searchTerm: event.target.value }, () => this.updateResults());
Hope I got it right.
Update:
Also I see many problems in your code, for example, why you update the list with a filtered list? you don't need to do that:
this.setState({
pokemonList: filteredList
});
Instead of updating the results in the state, you simply need to render the filtered list... meaning your state stay with the original list, also your filterd value, just in the render you pass the filtered list..
I need to display the confirm dialog using React.js before delete the item. I am explaining my code below.
import React, { Component } from "react";
import TodoItems from "./TodoItems";
import "./TodoList.css";
class TodoList extends Component {
constructor(props, context){
super(props, context);
this.state={
items:[]
}
this.addItem=this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.editItem = this.editItem.bind(this);
}
addItem(e){
e.preventDefault();
if(this.state.editKey){
this.saveEditedText();
return;
}
var itemArray = this.state.items;
if (this.inputElement.value !== '') {
itemArray.unshift({
text:this.inputElement.value,
key:Date.now()
})
this.setState({
items:itemArray
})
this.inputElement.value='';
}
}
deleteItem(key) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
editItem(key){
this.state.items.map(item =>{
if (item.key==key) {
this.inputElement.value=item.text;
}
})
this.setState({editKey: key});
}
saveEditedText(){
let value = this.inputElement.value;
this.setState(prevState => ({
items: prevState.items.map(el => {
if(el.key == prevState.editKey)
return Object.assign({}, el, {text: value});
return el;
}),
editKey: ''
}));
this.inputElement.value='';
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit={this.addItem}>
<input ref={(a)=>this.inputElement=a} placeholder="enter task">
</input>
<button type="submit">{this.state.editKey? "Update": "Add"}</button>
</form>
</div>
<TodoItems entries={this.state.items} delete={this.deleteItem} edit={this.editItem}/>
</div>
);
}
}
export default TodoList;
Here I need to display the confirm dialog box before deleting the item and also after adding/updating I need to display the success message just above the form using innerHTML/insertAdjacentElement using any div.
In the delete callback that is passed to the TodoItems, in this case deleteItem, you can prompt the user using the browser's confirm dialog:
const result = window.confirm(yourMessage)
and use result in a guard statement.
I have 2 files
App.js
import React, { Component } from 'react';
import './App.css';
import ToDo from './components/ToDo.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: [],
newTodoDescription: ''
};
this.deleteTodo = this.deleteTodo.bind(this);
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { id: this.state.todos.id, description: this.state.newTodoDescription, isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo], newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
deleteTodo(id) {
const remainingToDos = this.state.todos.filter((todo, remainingToDos) => {
if(todo.id !== remainingToDos.id) return todo; });
this.setState({ todos: remainingToDos });
}
render() {
return (
<div className="App">
<h1>Add a ToDo!</h1>
<form onSubmit={ (e) => this.handleSubmit(e)}>
<input type="text"
value={ this.state.newTodoDescription }
onChange={ (e) => this.handleChange(e) }
/>
<input type="submit" value="Add Todo" />
</form>
<ul>
{ this.state.todos.map( (todo) =>
<ToDo key={ todo.id }
description={ todo.description }
isCompleted={ todo.isCompleted }
toggleComplete={ () => this.toggleComplete(todo) }
onDelete={ this.deleteTodo }
/>
)}
</ul>
</div>
);
}
}
export default App;
ToDo.js
import React, { Component } from 'react';
class ToDo extends Component {
render() {
return (
<li>
<input type="checkbox" checked={ this.props.isCompleted } onChange={ this.props.toggleComplete } />
<span>{ this.props.description } {''}</span>
<button onClick={() => this.props.onDelete(this.props.id)}>Remove Todo</button>
</li>
);
}
}
export default ToDo;
What I am trying to accomplish: Add many todos to the list. Click on the remove todo button. ONLY the todo that is selected will be removed.
I am VERY new to react and cannot figure this out. In my deleteToDo method I am trying to filter out the todos and only keep the todos that are current. I am unclear if I am using .filter properly or not.
Problem is that filter() method should return a condition, not value:
deleteTodo(id) {
const remainingToDos = this.state.todos.filter((todo, remainingToDos) => {
return (todo.id !== remainingToDos.id)
});
this.setState({ todos: remainingToDos });
}
I am fairly sure you have simply over-complicated your code. You are not using the parameter id that you have passed into your method at all. Your deleteTodo method could simply be:
deleteTodo = (id) => {
var remainingToDos = this.state.todos.filter((todo) => {
return todo.id === id
});
this.setState({ todos: remainingToDos })
}
I have an input that is disable by default, but when I dispatch an action to enable it, it should become able. I also want this input to become focused, but I am not able to do that. Here is my component:
class UserInput extends Component {
constructor (props) {
super(props);
this.state = { responseValue: '' };
this.responseHandler = this.responseHandler.bind(this);
this.submitAnswer = this.submitAnswer.bind(this);
}
componentDidUpdate (prevProps) {
if (!this.props.disable && prevProps.disable) {
this.userInput.focus();
}
}
responseHandler (e) {
this.setState({ responseValue: e.target.value });
}
submitAnswer () {
this.props.submitUserAnswer(this.state.responseValue);
this.setState({ responseValue: '' })
}
render () {
return (
<div className="input-container">
<input ref={(userInput) => { this.userInput = userInput; }}
className="input-main"
disabled={this.props.disable}
value={this.state.responseValue}
onChange={this.responseHandler}
/>
<button className="input-button" onClick={this.submitAnswer}>{this.props.strings.SEND}</button>
</div>
);
}
}
UserInput.defaultProps = {
strings: {
'SEND': 'SEND',
},
};
UserInput.contextTypes = {
addSteps: React.PropTypes.func,
};
export default Translate('UserInput')(UserInput);
Thanks in advance!
I reckon your problem lies here:
if (!this.props.disable && prevProps.disable) {
this.userInput.focus();
}
this.props.disable will still be its initial value (false) after the update (it's not being updated by a parent component from what I can see) so the call to focus is never invoked.
I ended up doing this, because I needed to also add a placeholder to the disabled input:
class UserInput extends Component {
constructor (props) {
super(props);
this.state = { responseValue: '' };
this.responseHandler = this.responseHandler.bind(this);
this.submitAnswer = this.submitAnswer.bind(this);
}
responseHandler (e) {
this.setState({ responseValue: e.target.value });
}
submitAnswer () {
this.props.submitUserAnswer(this.state.responseValue);
this.setState({ responseValue: '' })
}
render () {
return (
<div className="input-container">
{ this.props.disable
? <div className="disable-input-box">Wait to type your response</div>
: <input
className="input-main"
disabled={this.props.disable}
value={this.state.responseValue}
onChange={this.responseHandler}
autoFocus
/>
}
<button className="input-button" onClick={this.submitAnswer}>{this.props.strings.SEND}</button>
</div>
);
}
}