How to filter out undefined from array in ts?

⌛3 min read
  • typescript
  • filter-undefined
  • tips
  • use-queries-filter
An Image which shows filtering of

So you want to filter out undefined from an array

But it keeps telling you that the result is an union of undefined | T. You might be tempted to use filter + map combo and a type guard along the way.
For a real world scenario let's use react-query.
It would roughly look like this.

Some starter code for react query.
import axios from "axios";
import { useQueries } from "@tanstack/react-query";

export type Todo = {
  id: number;
  todo: string;
  completed: boolean;
  userId: number;
};

async function fetchTodo(id: string): Promise {
  const { data } = await axios.get(`https://dummyjson.com/todos/${id}`);
  return data;
}

export function useTodoQueries(ids: string[]) {
  return useQueries({
    queries: ids.map((id) => {
      return {
        queryKey: ["todo", id],
        queryFn: () => fetchTodo(id),
      };
    }),
  });
}
Let's create queries based on ids from 1 to 10.
export type Todo = {
    id: number;
    todo: string;
    completed: boolean;
    userId: number;
  };

const whateverIds = new Array(10)
    .fill(0)
    .map((_, index) => (index + 1).toString());

  const todoQueries = useTodoQueries(whateverIds);
Let's get only defined todos from the queries the usual way
function filterAndMapResults(todoQueries: ReturnType) {
    // why check the type manually 🤖 Let's use ReturnType 🥸
    return (
      todoQueries
        // first map only the queries which have succedded
        .map((q) => q.data)
        // then coerce typescript with a type-guard so that data resolves as TODO
        .filter((data): data is Todo => {
          return !!data;
        })
    );
  }
Let's see now how reduce can work in our favor here
function reduceResults(todoQueries: ReturnType) {
    // why check the type manually 🤖 Let's use ReturnType 🥸

    const initialValue: Todo[] = [];

    return todoQueries.reduce((acc, current) => {
      if (current.data) {
        // only if data is defined add it into the result
        return acc.concat(current.data);
      }

      // otherwise return the previous accumulator
      return acc;
    }, initialValue);
  }

Performance concerns

For those who would be on the border here; let me explain myself.
I was not able to get constant results using the performance tab, but reduce version seemed to be around 0.1 ms slower when using 6x throttle (usually).
It's probably connected to the fact that each time a new place in memory has to be created for the brand new array.
On the other hand such concerns are rarely a thing for product devs'.
Let's face it a vast majority of time taken scripting in our apps is framework code.

Conclusion

My personal preference is the reduce way. Choose whichever way you prefer.
Remember!
Premature optimization is not what your employer wants you to take care of, your time is worth a lot 🐰.