Alban denica
Alban denica

Reputation: 137

multiple conditional renderings inside a map function

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

Upvotes: 2

Views: 330

Answers (5)

Taxel
Taxel

Reputation: 4207

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 always go with .filter.map chained. The benchmark also assumes the cheapest possible map function (just returning the name), for a more expensive map function the difference will be even smaller.

Upvotes: 0

Benjamin U.
Benjamin U.

Reputation: 638

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)}

Upvotes: 0

kennyKenny
kennyKenny

Reputation: 1

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>
    ))
}

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1074295

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.

Upvotes: 3

epascarello
epascarello

Reputation: 207511

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

Upvotes: 4

Related Questions