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 cant figure out why my Login component is not re-rendering what is on the page. The state is changed to loginShow after I click on a "Log In" button on top of my form.
import React from 'react';
import SignUp from '../SignUp';
import Cards from '../Articles/Cards';
import Login from '../LogIn';
export default class Gecko extends React.Component {
constructor(props) {
super(props);
this.state = { requestedPostsThatWeGotFromGecko: null, }
this.clickMe = this.clickMe.bind(this)
this.state = {
loginStatus: "loginHide",
authType: "signup"
};
this.loginClose = this.loginClose.bind(this);
this.loginOpen = this.loginOpen.bind(this);
}
loginClose(e) {
e.stopPropagation();
this.setState({
loginStatus: "loginHide"
});
}
loginOpen() {
this.setState({
loginStatus: "loginShow"
});
}
clickMe = () => {
const {requestedPostsThatWeGotFromGecko} = this.state;
this.setState({ requestedPostsThatWeGotFromGecko: !requestedPostsThatWeGotFromGecko })
}
/* mainLogIn = () => {
const {loginStatus} = this.state;
this.setState({ loginStatus: 'loginHide' })
} */
render() {
const { requestedPostsThatWeGotFromGecko } = this.state;
const { loginStatus } = this.state;
console.log(loginStatus);
// const { authType } = this.state;
// if (loginStatus === "loginHide") return <SignUp login={() => this.clickMe()} />
// if (requestedPostsThatWeGotFromGecko.props === true) return <Cards />
if (loginStatus !== "loginShow") return (
<div className="gecko">
{requestedPostsThatWeGotFromGecko ? (
<Cards />
): (
<SignUp login={() => this.clickMe()} />
)
}
</div>
);
if (loginStatus === "loginShow") return <Login />
}
}
I believe the issue lies where I am trying to set what is being displayed using the conditional operator at the end of the code above. I have included where I am setting the state and props from my other components in the code below.
const onClick = () => {
this.props.login();
console.log('rich');
}
const mainLogIn = () => {
this.setState({
loginStatus: "loginShow"
});
console.log('rich1');
console.log(this.state);
}
const onClick = () => {
this.props.login();
console.log('rich');
}
const mainSignUp = () => {
this.props.signup();
this.setState({
loginStatus: "loginHide"
});
console.log('rich2');
}
In the output, Only the default completed values are checked! not able change the checks of tasks.
These are my Java script files
app.js
class App extends Component {
constructor() {
super()
this.state = {
todos: Todosdata
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const udpated = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: udpated
}
})
}
render() {
const todoelements = this.state.todos.map(item => <ToDoItem key={item.id}
todoitem={item}
handleChange={this.handleChange} />)
return (
<div className="App">
< div className="todo-list" >
{todoelements}
</div >
</div>
)
}
}
ToDoItem.js
const ToDoItem = (props) => {
const Afterstyle = {
fontColor: "red",
textDecoration: "line-through"
}
return (
<div className="todo-item">
<input type="checkbox"
checked ={props.todoitem.completed}
onChange ={() => props.handleChange(props.todoitem.id)} />
<p style={props.todoitem.completed ? Afterstyle : null}>{props.todoitem.task}</p>
</div>
)
}
i did console log inside if condition of handle Change method,its printing 2 times.
I am stuck at this for hours please fix this!
You are mutating the todo item instead of creating a new one. Change your handler like that:
handleChange(id) {
this.setState(prevState => {
const udpated = prevState.todos.map(todo => {
if (todo.id === id) {
// if the id matches return a new object
return {...todo, completed: !todo.completed};
}
return todo
});
return {todos: udpated};
}
}
Live Demo:
I have a class component, within that calling a function. I have a state variable and want to update the state in function. Since it is different function I'm not able to update the value. How can I get the selected items details and update the state? when I do setState, receiving following error as 'TypeError: this.setState is not a function'
any help appreciated
Component
import React, { Component } from 'react'
import PropTypes from "prop-types";
import statedist from "./StateDistrict.json";
const suggestions = statedist.states;
function DownshiftMultiple(props) {
const { classes } = props;
const [inputValue, setInputValue] = React.useState("");
const [selectedItem, setSelectedItem] = React.useState([]);
function handleKeyDown(event) {
if (
selectedItem.length &&
!inputValue.length &&
event.key === "Backspace"
) {
setSelectedItem(selectedItem.slice(0, selectedItem.length - 1));
}
}
function handleInputChange(event) {
setInputValue(event.target.value);
}
function handleChange(item) {
let newSelectedItem = [...selectedItem];
if (newSelectedItem.indexOf(item) === -1) {
newSelectedItem = [...newSelectedItem, item];
}
setInputValue("");
setSelectedItem(newSelectedItem);
this.setState({ SelectedState: newSelectedItem }); // here i want to update selected items
}
const handleDelete = item => () => {
const newSelectedItem = [...selectedItem];
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
setSelectedItem(newSelectedItem);
};
return (
<Downshift
id="downshift-multiple"
inputValue={inputValue}
onChange={handleChange}
selectedItem={selectedItem}
>
{({
getInputProps,
getItemProps,
getLabelProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex
}) => {
const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({
onKeyDown: handleKeyDown,
// placeholder: "Select multiple State"
});
return (
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
// label: "States",
InputLabelProps: getLabelProps(),
InputProps: {
startAdornment: selectedItem.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
className={classes.chip}
onDelete={handleDelete(item)}
/>
)),
onBlur,
onChange: event => {
handleInputChange(event);
onChange(event);
},
onFocus
},
inputProps
})}
{isOpen ? (
<Paper className={classes.paper} square>
{getSuggestions(inputValue2).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion.state }),
highlightedIndex,
selectedItem: selectedItem2
})
)}
</Paper>
) : null}
</div>
);
}}
</Downshift>
);
}
class autoCompleteState extends Component {
constructor(props) {
super(props);
this.state = {
SelectedState:'',
}
// this.showProfile = this.showProfile.bind(this)
}
render() {
const { classes, } = this.props;
return (
<div>
<DownshiftMultiple classes={classes} />
</div>
)
}
}
export default withStyles(Styles)(autoCompleteState);
You can't and shouldn't access the context (this) of other components directly to update its state, especially not with a functional component.
What you have to do is pass a function as a prop to your DownshiftMultiple component which itself gets the value with which you want to update the state.
function DownshiftMultiple(props) {
/* ... */
function handleChange(item) {
let newSelectedItem = [...selectedItem];
if (newSelectedItem.indexOf(item) === -1) {
newSelectedItem = [...newSelectedItem, item];
}
setInputValue("");
setSelectedItem(newSelectedItem);
this.props.onChange(newSelectedItem); // Use the new function prop
}
/* ... */
}
class autoCompleteState extends Component {
/* ... */
onDMChange = (newSelectedItem) => this.setState({ SelectedState: newSelectedItem });
render() {
const { classes, } = this.props;
return (
<div>
<DownshiftMultiple classes={classes} onChange={this.onChange} />
</div>
)
}
}
Also on a sidenote I would recommend to encapsulate your event handling functions inside your functional DownshiftMultiple component with the useCallback hook. Something like const newSelectedItem = [...selectedItem]; would always use the value that the state has been initialised with without a hook.
// For example your handle delete
const handleDelete = React.useCallback(item => () => {
const newSelectedItem = [...selectedItem];
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
setSelectedItem(newSelectedItem);
}, [selectedItem]);
You pass on a handler to the child component, which it will invoke with the value to update and the update action happens in the parent
import React, { Component } from 'react'
import PropTypes from "prop-types";
import statedist from "./StateDistrict.json";
const suggestions = statedist.states;
function DownshiftMultiple(props) {
const { classes } = props;
const [inputValue, setInputValue] = React.useState("");
const [selectedItem, setSelectedItem] = React.useState([]);
function handleKeyDown(event) {
if (
selectedItem.length &&
!inputValue.length &&
event.key === "Backspace"
) {
setSelectedItem(selectedItem.slice(0, selectedItem.length - 1));
}
}
function handleInputChange(event) {
setInputValue(event.target.value);
}
function handleChange(item) {
let newSelectedItem = [...selectedItem];
if (newSelectedItem.indexOf(item) === -1) {
newSelectedItem = [...newSelectedItem, item];
}
setInputValue("");
setSelectedItem(newSelectedItem);
props.setSelectedState(newSelectedItem);
}
const handleDelete = item => () => {
const newSelectedItem = [...selectedItem];
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
setSelectedItem(newSelectedItem);
};
return (
<Downshift
id="downshift-multiple"
inputValue={inputValue}
onChange={handleChange}
selectedItem={selectedItem}
>
{({
getInputProps,
getItemProps,
getLabelProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex
}) => {
const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({
onKeyDown: handleKeyDown,
// placeholder: "Select multiple State"
});
return (
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
// label: "States",
InputLabelProps: getLabelProps(),
InputProps: {
startAdornment: selectedItem.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
className={classes.chip}
onDelete={handleDelete(item)}
/>
)),
onBlur,
onChange: event => {
handleInputChange(event);
onChange(event);
},
onFocus
},
inputProps
})}
{isOpen ? (
<Paper className={classes.paper} square>
{getSuggestions(inputValue2).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion.state }),
highlightedIndex,
selectedItem: selectedItem2
})
)}
</Paper>
) : null}
</div>
);
}}
</Downshift>
);
}
class autoCompleteState extends Component {
constructor(props) {
super(props);
this.state = {
SelectedState:'',
}
// this.showProfile = this.showProfile.bind(this)
}
setSelectedState = (newState) => {
this.setState({ SelectedState: newState });
}
render() {
const { classes, } = this.props;
return (
<div>
<DownshiftMultiple classes={classes} setSelectedState={this.setSelectedState}/>
</div>
)
}
}
export default withStyles(Styles)(autoCompleteState);
I am trying to set up validations in my todo list app in react; however this doesn't seem to work. Even if the form is empty the process still goes through and I don't know where the problem is coming from. I am just following a tutorial online since I'm pretty new to this and don't know how to do it myself.
import React from "react";
import * as TodoActions from "../actions/TodoActions";
import TodoStore from "../stores/TodoStore";
import Todo from './Todo.js'
import './Todos.css'
const formValid = ({ formErrors, ...rest }) => {
let valid = true;
Object.values(formErrors).forEach(val => {
val.length > 0 && (valid = false);
});
Object.values(rest).forEach(val => {
val === null && (valid = false);
});
return valid;
};
export default class Todos extends React.Component {
constructor() {
super();
this.state = {
todos: TodoStore.getAll(),
loading: true,
formErrors: {
todo: ""
}
};
TodoActions.receiveTodos()
}
componentWillMount() {
TodoStore.addChangeListener(this.getTodos);
}
componentWillUnmount() {
TodoStore.removeChangeListener(this.getTodos);
}
componentDidUpdate() {
TodoActions.receiveTodos();
}
getTodos = () => {
this.setState({
todos: TodoStore.getAll(),
loading: false
});
}
deleteTodo = (id) => {
TodoActions.deleteTodo(id);
}
addItem = (e) => {
e.preventDefault();
TodoActions.createTodo(this._inputElement.value)
}
handleChange = e => {
e.preventDefault();
const { name, value } = e.target;
let formErrors = { ...this.state.formErrors };
switch (name) {
case "todo":
formErrors.todo =
value.length < 0 ? "Task cannot be empty" : "";
break;
default:
break;
}
this.setState({ formErrors, [name]: value }, () => console.log(this.state));
}
render() {
const { todos } = this.state;
const { formErrors } = this.state;
let TodoComponents;
if (this.state.loading) {
TodoComponents = <h1>Loading...</h1>;
} else if(todos.length) {
TodoComponents = todos.map((todo) => {
return (
<div key={todo.id} className="todo-list">
<Todo key={todo.id} name={todo.name}/>
<div className="todo-btn"><a type="button" onClick={() => this.deleteTodo(todo.id)} className="delete-btn"><i class="fas fa-trash-alt"></i></a></div>
</div>
)
});
} else {
TodoComponents = <p>No tasks to show :)</p>
}
return (
<div className="main-container">
<div className="small-container">
<h1 className="title">All Tasks</h1>
<ul>{TodoComponents}</ul>
<form onSubmit={this.addItem}>
<input ref={(a) => this._inputElement = a} placeholder="Enter Task" className="input-form {formErrors.todo.length < 0 ? 'error' : null}"/>
{formErrors.todo.length < 0 && (
<span className="errorMessage">{formErrors.firstName}</span>
)}
<button type="submit" className="input-btn">Add</button>
</form>
</div>
</div>
);
}
}