Reputation: 160
I'm new to React and I have followed a tutorial for a ToDo web app. After completion, I built it for production without errors in the terminal. The issue occurs when I try to statically open with a serve -s build
in order to see it locally. Initially, I get a glimpse of the content and then two errors popup that were not there in development with npm start server
.
The errors are:
react-dom.production.min.js:209 TypeError: Cannot read property 'length' of null
at App.js:31
at ro (react-dom.production.min.js:211)
at vu (react-dom.production.min.js:257)
at t.unstable_runWithPriority (scheduler.production.min.js:19)
at Wl (react-dom.production.min.js:122)
at hu (react-dom.production.min.js:257)
at react-dom.production.min.js:256
at j (scheduler.production.min.js:17)
at MessagePort.E.port1.onmessage (scheduler.production.min.js:14)
Za @ react-dom.production.min.js:209
scheduler.production.min.js:14 Uncaught TypeError: Cannot read property 'length' of null
at App.js:31
at ro (react-dom.production.min.js:211)
at vu (react-dom.production.min.js:257)
at t.unstable_runWithPriority (scheduler.production.min.js:19)
at Wl (react-dom.production.min.js:122)
at hu (react-dom.production.min.js:257)
at react-dom.production.min.js:256
at j (scheduler.production.min.js:17)
at MessagePort.E.port1.onmessage (scheduler.production.min.js:14)
From the message I understand that the faulty part is in my
App.js
import React, { useState } from "react";
import "./App.css";
//Importing Components
import Form from "./Components/Form";
import ToDoList from "./Components/ToDoList";
function App() {
// eslint-disable-next-line
const [inputText, setInputText] = useState("");
const [todos, setTodos] = useState([]);
const [status, setStatus] = useState("");
const [filterTodos, setFilterTodos] = useState([]);
const getLocalTodos = () => {
setTodos(JSON.parse(localStorage.getItem("todos")));
};
React.useEffect(() => {
switch (status) {
case "completed":
setFilterTodos(todos.filter((todo) => todo.completed === true));
break;
case "uncompleted":
setFilterTodos(todos.filter((todo) => todo.completed === false));
break;
default:
setFilterTodos(todos);
break;
}
if (todos.length !== 0) {
localStorage.setItem("todos", JSON.stringify(todos));
}
}, [todos, status]);
React.useEffect(() => {
getLocalTodos();
}, []);
return (
<React.Fragment>
<header>{inputText}</header>
<Form
setStatus={setStatus}
todos={todos}
setTodos={setTodos}
InputText={inputText}
setInputText={setInputText}
/>
<ToDoList filterTodos={filterTodos} setTodos={setTodos} todos={todos} />
</React.Fragment>
);
}
export default App;
the part where I am saving the data to the localStorage. I'm checking the array against zero in order to prevent useEffect from emptying the localStorage data on the first load. How to fix the issue in the production version?
Below I have attached the rest of the code:
ToDoList.js
import React from "react";
import ToDo from "./ToDo";
const ToDoList = (props) => {
return (
<div className="todo-container">
<ul className="todo-list">
{props.filterTodos.map((todo) => {
return (
<ToDo
todo={todo}
todos={props.todos}
setTodos={props.setTodos}
key={todo.id}
text={todo.text}
/>
);
})}
</ul>
</div>
);
};
export default ToDoList;
ToDo.js
import React from "react"
const ToDo = props => {
const deleteNote = () => {
props.setTodos(props.todos.filter(e=>e.id !== props.todo.id))
}
const completedHandler = () =>{
props.setTodos(
props.todos.map(item =>{
if(item.id === props.todo.id){
return {
...item, completed: !item.completed
}
}
return item
})
)
}
return (
<div className="ToDo">
<li className={`todo-item ${props.todo.completed ? "cool" : "notCool"}`}>{props.text}</li>
<button onClick={completedHandler} className='complete-btn'>
Complete </button>
<button onClick={deleteNote} className='trash-btn'>
Trash </button>
</div>
)
}
export default ToDo
Form.js
import React from "react";
const Form = (props) => {
const InputTextHandler = (e) => {
props.setInputText(e.target.value);
};
const addNote = (e) => {
e.preventDefault();
props.setTodos([
...props.todos,
{ text: props.InputText, completed: false, id: Math.random() },
]);
props.setInputText("");
console.log(props.InputText);
};
const statusHanlder = (e) => {
props.setStatus(e.target.value);
};
return (
<form>
<input
value={props.InputText}
onChange={InputTextHandler}
type="text"
className="todo-input"
/>
<button onClick={addNote} className="todo-button" type="submit">
<i className="fas fa-plus-square">+</i>
</button>
<div className="select">
<select onChange={statusHanlder} name="todos" className="filter-todo">
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="uncompleted">Uncompleted</option>
</select>
</div>
</form>
);
};
export default Form;
Development view
Production version in a static local server
Upvotes: 3
Views: 4211
Reputation: 1961
When first time rendering App. You get todos list from localStorage. But it nothing. So, your code always set todos and that case totos will be null
.
Component App should check:
const getLocalTodos = () => {
const cached = localStorage.getItem("todos") ? JSON.parse(localStorage.getItem("todos")) : null;
if (cached === null)
return;
setTodos(cached);
};
Upvotes: 2
Reputation: 535
If there is no todos field inside localStorage then useEffect with getLocalTodos call sets "null" value for todos state.
Just change getLocalTodos like that:
const getLocalTodos = () => {
setTodos(JSON.parse(localStorage.getItem("todos")) || []);
};
Upvotes: 3
Reputation: 4938
Are you sure you have todos in your localStorage?
This function assumes you have todos in your localStorage which is probably not.
const getLocalTodos = () => {
setTodos(JSON.parse(localStorage.getItem("todos")));
};
Without your todos in the localStorage
JSON.parse(localStorage.getItem("todos")) // returns undefined.
which messes up your todos state which is meant to be an array.
Hence .length
of undefined.
Upvotes: 1