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
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")) || []);
};