Home > Enterprise >  How to push Components to two different divs during mapping in React?
How to push Components to two different divs during mapping in React?

Time:12-19

In React I have an array of objects as a state. Let's say they are "todos". I also have two divs. One div is for incomplete tasks, second is for completed tasks. During mapping I'm using <Todo/> component.

const [todos, setTodos] = useState([
    {id: 1, text: 'Lorem', isComplete: true},
    {id: 2, text: 'Ipsum', isComplete: true}],
    {id: 3, text: 'Test', isComplete: false})
]);

in return I have an HTML structure:

return (     
    <div>
         <h1>Todos</h1>
         <p>My todos for today:</p>
         <div id="incomplete"></div> 
         <p>Todos that've been already done:</p>
         <div id="complete"></div>     
    </div> )

Mapping looks something like this:

todos.map(todo => {     
    if(todo.isComplete){
         return <Todo 
                     id={todo.id} 
                     text={todo.text} 
                     isComplete={todo.isComplete}/>         
// AND HERE I WANT TO PUSH THIS COMPONENT INTO div#complete.     
} })

I'm expecting something like this:

return (
     <div>         
         <h1>Todos</h1>
         <p>My todos for today:</p>
         <div id="incomplete">
             <div className="task">
                <p>Test</p>
             </div>
             <div className="task">
                <p>Ipsum</p>
             </div>
         </div>
         <p>Todos that've been already done:</p>
         <div id="complete">
             <div className="task">
                <p>Lorem</p>
             </div>
         </div>
     </div> )

CodePudding user response:

Use filter() before map() to get only the (in)/complete:

return (     
    <div>
         <h1>Todos</h1>
         <p>My todos for today:</p>
         <div id="incomplete">
                {todos.filter(t => !t.isComplete).map(e => <Todo ...{} />)}
         </div> 
         <p>Todos that've been already done:</p>
         <div id="complete">
                {todos.filter(t => t.isComplete).map(e => <Todo ...{} />)}
         </div>     
    </div> 
)

React Demo:

const { useState } = React;

const Todo = (props) => {
    return <p>{props.text}</p>;
}

const Example = () => {

    const [todos, setTodos] = useState([
        {id: 1, text: 'Lorem', isComplete: true},
        {id: 2, text: 'Ipsum', isComplete: true},
        {id: 3, text: 'Test', isComplete: false}
    ]);

    return (     
        <div>
            <h1>Todos</h1>
            <p>My todos for today:</p>
            <div id="incomplete">
                {todos.filter(t => !t.isComplete).map(e => <Todo {...e} />)}
             </div> 
             <p>Todos that've been already done:</p>
             <div id="complete">
                {todos.filter(t => t.isComplete).map(e => <Todo {...e} />)}
             </div>     
        </div> 
    );
}

ReactDOM.render(<Example />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>


Based on OP's edit, you can introduce a function like getTodos that will accept a boolean, and create the filter function based on that. Then return <Todo /> accordingly, this will reduce the duplicate filter(), map() and <Todo ... /> so you'll just need these 2 in the render:

<div id="incomplete">
    {getTodos()}
</div> 
// ....
<div id="complete">
    {getTodos(true)}
</div>   

React Demo:

const { useState } = React;

const Todo = (props) => {
    return <p>{props.text}</p>;
}

const Example = () => {

    const [todos, setTodos] = useState([
        {id: 1, text: 'Lorem', isComplete: true},
        {id: 2, text: 'Ipsum', isComplete: true},
        {id: 3, text: 'Test', isComplete: false}
    ]);
    
    const getTodos = (complete) => {
        const filterFunction = (complete) ? (t => t.isComplete) : (t => !t.isComplete);
        return todos.filter(filterFunction).map(e => {
            return <Todo {...e} />;
        });
    }

    return (     
        <div>
            <h1>Todos</h1>
            <p>My todos for today:</p>
            <div id="incomplete">
                {getTodos()}
             </div> 
             <p>Todos that've been already done:</p>
             <div id="complete">
                {getTodos(true)}
             </div>     
        </div> 
    );
}

ReactDOM.render(<Example />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

CodePudding user response:

You can write an function to create the required jsx based on the task status use that for rendering the tasks please refer the code snippet below

const [todos, setTodos] = useState([
    { id: 1, text: "task1", isComplete: false },
    { id: 2, text: "task2", isComplete: false },
    { id: 3, text: "task3", isComplete: true }
  ]);

  const tasks = () => {
    let completeTasks = [];
    let incompleteTasks = [];
    todos.forEach((todo) => {
      if (todo.isComplete) {
        completeTasks.push(
          <div className="task">
            <p>{todo.text}</p>
          </div>
        );
      } else {
        incompleteTasks.push(
          <div className="task">
            <p>{todo.text}</p>
          </div>
        );
      }
    });

    return { complete: completeTasks, incomplete: incompleteTasks };
  };

  const todoElements = tasks();

  return (
    <div className="App">
      <h1>Todos</h1>
      <div>
        <p>My todos for today:</p>{" "}
        <div id="incomplete">{todoElements.incomplete}</div>
        <p>Todos that've been already done:</p>
        <div id="complete">{todoElements.complete}</div>
      </div>
    </div>
  );

please refer the sandbox : https://codesandbox.io/s/quizzical-leavitt-utt31m?file=/src/App.js:91-1107

CodePudding user response:

@0stone0 Thank you for an answer. Sorry, I haven't been precise. I've done this approach, but I wanted to make it more clear and don't repeat returning <Todo .../> component in two places.

Is it possible?

This is my code and it doesn't look very pretty tbh

    <div className='tasks'>
      <h2>Tasks</h2>
      <div className="tasks-incomplete">
        <h3>Zadania do wykonania</h3>
        {incompleteTasks.length > 0 && incompleteTasks.map(task => (
          <Task 
            key={task.id}
            id={task.id} 
            text={task.text} 
            isComplete={task.isComplete} 
            dateStart={task.dateStart}
            dateComplete={task.dateComplete}
            handleDeleteTask={() => handleDeleteTask(task.id)}
            handleClickEdit={()=>handleClickEdit(task.id, task.text)}
            handleComplete={()=>handleComplete(task.id)}
          />
          ))}
        {incompleteTasks.length === 0 && <p>brak zadań</p>}
      </div>
      
      {completeTasks.length > 0 && (
        <div id="completed" className="tasks-complete">
          <h3>Zadania wykonane</ h3>
          {completeTasks.map( task => (
            // todo: task do destrukturyzacji
            <Task 
              key={task.id}
              id={task.id} 
              text={task.text} 
              isComplete={task.isComplete} 
              dateStart={task.dateStart}
              dateComplete={task.dateComplete}
              handleDeleteTask={()=>handleDeleteTask(task.id)}
              handleClickEdit={()=>handleClickEdit(task.id, task.text)}
              handleComplete={()=>handleComplete(task.id)}
            /> 
          ))}
        </div>
      )}
      
    </div>
  )

  • Related