I'm learning react at the moment and currently, making a todo app so that I can understand react more easily. So here's what I'm trying to do:

  • The user clicks a button
  • The click fires a prompt which asks the user for the todo title (only title at the moment)
  • Then, that title is added to an array of all todos
  • And then, use that array to display each todo on the page

Code:

const [check, setCheck] = useState(false);
const [todo, setTodo] = useState([]);

function handleClick() {
    let toAdd = prompt('Title: ')
    setTodo([...todo, {
        title: toAdd
    }]);
}

useEffect(()=> {
    if(todo.length !== 0) {
        setCheck(true);
    }
})

return (
    <div className="wholeContainer">
        <div className="tododiv">
            <span className="todos">Todos: </span>
            <hr/>
            {
                check ? 
                todo.forEach((eachTodo)=> {
                    <TodoItems title={eachTodo}/>
                })
                : <span>Nothing</span>
                    
            }
        </div>
        <button className="add" onClick={handleClick}>
        <i className="fas fa-plus"></i>
            Add a Todo
        </button>
    </div>
);

The const [check, setCheck] = useState(false); is written so that I can access the array if todo.length !== 0;

The problem comes in the rendering part. I can't figure out a way to display each and every todo in their own <TodoItems/> component, and also when using forEach(), nothing happens because I think that someone told me that setState() works asynchronously. I really need some advice!

Thanks...


You are using

todo.forEach((eachTodo)=> {
  <TodoItems title={eachTodo}/>
})

When you should be using

todo.map((eachTodo)=> {
  return <TodoItems title={eachTodo}/>
})

Or

todo.map((eachTodo)=> (
  <TodoItems title={eachTodo}/>
))

Also you have an infinite loop in your component:

useEffect(()=> {
    if(todo.length !== 0) {
        setCheck(true);
    }
})

Each time the component updates, when the todo list isn't empty, you setCheck to true which triggers a new render. Also, you don't need to use state for every variable, only the ones were a change should trigger a re-render.

Also your new todo-list state depends on the previous state so you should use a functional update.

https://reactjs.org/docs/hooks-reference.html

const [todoList, setTodoList] = useState([]);

function handleClick() {
    let toAdd = prompt('Title: ');
    setTodoList((prevTodoList) => [...prevTodoList, toAdd]);
}

const isTodoListEmpty = todoList.length === 0
return (
    <div className="wholeContainer">
        <div className="tododiv">
            <span className="todos">Todos: </span>
            <hr />
            {!isTodoListEmpty ? (
                todoList.forEach((todoItem) => {
                    <TodoItems title={todoItem} />;
                })
            ) : (
                <span>Nothing</span>
            )}
        </div>
        <button className="add" onClick={handleClick}>
            <i className="fas fa-plus"></i>
            Add a Todo
        </button>
    </div>
);