Search code examples
javascriptreactjsreact-hooksreact-statereact-state-management

Appending a div in React using React hooks


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...


Solution

  • 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>
    );