Home > Blockchain >  multiple conditional renderings inside a map function
multiple conditional renderings inside a map function

Time:02-12

Hi there I'm looking if there's a better way to render my todos

I have this

         {
            todos.map((todo) => (
                
                todo.status === 1 && (
                    <p>{todo.title}</p>
                )
            ))
        }
        {
            todos.map((todo) => (
                todo.status === 2 && (
                    <p>{todo.title}</p>
                )
            ))
        }
        {
            todos.map((todo) => (
                todo.status === 3 && (
                    <p>{todo.title}</p>
                )
            ))
        }

Is there a way to do this?

Thanks a lot

CodePudding user response:

If you want them to be in order, then sort them before you map them

todos.sort((a, b) => a.status - b.status).map((todo) => .....)

if you do not want to change the order of the original array then copy it

todos.slice().sort((a, b) => a.status - b.status).map((todo) => .....)

CodePudding user response:

I'm assuming they really are all right next to each other like that. If so:, either:

  1. Sort todos before rendering

  2. Use an outer loop of status values

Sort

Sort the todos array, ideally just the once prior to rendering/re-rendering.

todos.sort((a, b) => a.status - b.status);

Note that this assumes there are only those three status values. If there are others, you may want to have a separate filtered and sorted array that you rebuild when todos changes.

Outer loop

{[1, 2, 3].map(
    status => todos.map(
        todo => todo.status === status && <p>{todo.title}</p>
    )
)}

Side note: Those p elements need keys.

CodePudding user response:

You can filter those todos first then render later

todos.filter(todo => todo.status === 1).map(todo => (
    <p>{todo.title}</p>
))

todos.filter(todo => todo.status === 2).map(todo => (
    <p>{todo.title}</p>
))

todos.filter(todo => todo.status === 3).map(todo => (
    <p>{todo.title}</p>
))

or create a reuseable function to render those todos

function renderTodoWithStatus(todos, status) {
    return todos.filter(todo => todo.status === status).map(todo => (
        <p>{todo.title}</p>
    ))
}

CodePudding user response:

Making no assumptions about the status, you could do something like this:

const getTodos = (status) => todos
  .filter((todo) => todo.status === status)
  .map((todo) => <p>{todo.title}</p>);
...
{getTodos(1)}
{getTodos(2)}
{getTodos(3)}

CodePudding user response:

Another possibility (assuming you want to e.g. put <h2> headings between the different types of todos) would be to use filter:

// get all todos with status 1
todos.filter(todo => todo.status === 1).map(todo => (<p key={todo.id}>{todo.title}</p>))

If you want to show them as a continuous array, the sort approach is better.

EDIT: Since @epascarello insisted that more loops were bad, I wrote a quick little benchmark (takes a while before the console output starts showing up, simplified not to use React)

const REPEAT_TESTS = 1000;


function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}

for (let elements of [1, 10, 100, 1000, 100000]) {
  console.warn(`trying all three approaches with ${elements} todo items`);
  const array = [];
  for (let i = 0; i < elements; i  ) {
    array.push({
      title: "item "   i,
      status: getRandomInt(3)   1
    })
  }

  // map only
  let start = performance.now();
  for (let i = 0; i < REPEAT_TESTS; i  ) {
    const result1 = array.map(elem => (elem.status === 1 && elem.title));
    const result2 = array.map(elem => (elem.status === 2 && elem.title));
    const result3 = array.map(elem => (elem.status === 3 && elem.title));
  }
  let elapsed = performance.now() - start;
  console.log(`map only took ${elapsed} ms (${elapsed/REPEAT_TESTS}ms per iteration)`);

  // .filter.map()
  start = performance.now();
  for (let i = 0; i < REPEAT_TESTS; i  ) {
    const result1 = array.filter(elem => elem.status === 1).map(elem => elem.title);
    const result2 = array.filter(elem => elem.status === 2).map(elem => elem.title);
    const result3 = array.filter(elem => elem.status === 3).map(elem => elem.title);
  }
  elapsed = performance.now() - start;
  console.log(`.filter().map() took ${elapsed} ms (${elapsed/REPEAT_TESTS}ms per iteration)`);

  // test 3
  start = performance.now();
  for (let i = 0; i < REPEAT_TESTS; i  ) {
    const sorted = array.slice().sort((a, b) => a.status - b.status).map(elem => elem.title);
  }
  elapsed = performance.now() - start;
  console.log(`Test 3 took ${elapsed} ms (${elapsed/REPEAT_TESTS}ms per iteration)`);
}
This clearly shows that for todos.length >= 100

  • OPs solution is fastest (because yes, the filter adds some loops. But OPS solution also returns false for all the non matching elements which is completely unusable in data processing and might incur some performance penalty in React's handling of the values)
  • .filter.map is slightly slower but more readable and does not have the problem of returning false values
  • sort is slow for more than a couple of elements and gets slower quickly. I tried this example in Nodejs with 10.000.000 elements and these were the results:
map only took 42667.75609499961 ms (42.66775609499961ms per iteration)
.filter.map took 56544.36275900155 ms (56.54436275900155ms per iteration)
sort took 107536.24900600314 ms (107.53624900600315ms per iteration)

In conclusion I would go

  • Related