Typescript: How I Add an Object to an Array Only When I Need To

I’m Kayla, and I write code for a living. I also review it like a picky friend. This tiny thing—“conditionally add object to array”—sounds simple. It bit me more than once. Extra items. Weird types. A crash on a Tuesday. You know what? I finally got a set of patterns that feel clean, safe, and not fussy.

Let me explain, and I’ll show real code I use in my projects. Pro tip: there’s a deeper dive with more examples over on Improving Code if you want to sharpen this skill.

For an even more focused tutorial, see my detailed walkthrough: TypeScript: How I Add an Object to an Array Only When I Need To.

By the way, I’m a fan of brutally honest reviews in every corner of the internet, not just in code. If you’ve ever wondered whether certain chat-and-date apps actually deliver on their promises, take a look at this no-nonsense SweetSext review. It cuts through the marketing hype with pros, cons, and real user insights so you can decide in minutes whether the platform is worth your time.

Building on that spirit of transparency, anyone curious about the escort and dating landscape in Arizona’s Pinal County should check out this in-depth local guide to adult listings: AdultLook Casa Grande review. You’ll find a candid breakdown of pricing, profile verification, and safety tips so you can navigate the scene confidently and avoid common pitfalls.

The quick story

I was building a list for a shopping app. We had a base set of items. If the user had a coupon, we would add a “bonus” item. If not, we wouldn’t. Sounds easy. But my first pass left undefined in the array. React hated that. Typescript nagged me. I fixed it with a few simple tricks.

Here’s what I reach for now.


1) The plain old if + push (mutates the array)

Simple. Clear. Boring in a good way.

type Item = { id: string; name: string };

const items: Item[] = [{ id: "base-1", name: "Base" }];

const hasCoupon = true;
const bonus: Item = { id: "bonus-1", name: "Bonus" };

if (hasCoupon) {
  items.push(bonus);
}

console.log(items);
// [{ id: "base-1", name: "Base" }, { id: "bonus-1", name: "Bonus" }]

Why I like it:

  • Easy to read.
  • No trickery.

What to watch:

  • It changes the original array. That’s fine in many spots, but not in React state.

Tiny warning: don’t do hasCoupon && items.push(bonus) unless you love weird return values (push returns a number). It works, but it’s messy.

If you’re wondering how a classic loop stacks up against these push patterns, I ran the numbers in my hands-on take on TypeScript’s forEach loop.


2) The spread trick (makes a new array)

This is my go-to in React setState, or when I want to keep things pure.

type Item = { id: string; name: string };

const base: Item[] = [{ id: "base-1", name: "Base" }];
const hasCoupon = false;
const bonus: Item = { id: "bonus-1", name: "Bonus" };

const items = [
  ...base,
  ...(hasCoupon ? [bonus] : []),
];

console.log(items);
// [{ id: "base-1", name: "Base" }]

Why it’s nice:

  • No mutation.
  • Reads like, “add this only if the flag is true.”

3) Build first, then filter out “maybe” values (with a safe filter)

Sometimes I build a list with a “maybe” object. Then I clean it up.

type Item = { id: string; name: string };

const base: Item = { id: "base-1", name: "Base" };
const hasCoupon = true;
const bonus: Item | undefined = hasCoupon ? { id: "bonus-1", name: "Bonus" } : undefined;

// A safe type guard so TS knows we removed undefined/null
const isDefined = <T>(x: T | undefined | null): x is T => x != null;

const items = [base, bonus].filter(isDefined);

console.log(items);
// [{ id: "base-1", name: "Base" }, { id: "bonus-1", name: "Bonus" }]

Note: People use filter(Boolean). It can drop values like 0 or "". That’s not always good. I use isDefined so types stay tight, and only null/undefined get removed. For an excellent exploration of this pattern, Ben Ilegbodu demonstrates practical strategies for filtering undefined elements from an array in TypeScript | Ben Ilegbodu.


4) A tiny helper: pushIf

I use this when I need clarity. It keeps code neat in lists.

function pushIf<T>(arr: T[], cond: boolean, value: T) {
  if (cond) arr.push(value);
}

type Item = { id: string; name: string };

const items: Item[] = [{ id: "base-1", name: "Base" }];
const premiumUser = true;

pushIf(items, premiumUser, { id: "pro-1", name: "Pro Tips" });

console.log(items);
// [{ id: "base-1", name: "Base" }, { id: "pro-1", name: "Pro Tips" }]

It reads well in long builders. I like that.

And if you want an unfiltered opinion on where forEach still wins—or completely falls flat—check out my honest take on the TypeScript forEach loop.


5) State updates in React: keep it pure and tidy

This is where the spread pattern shines. I use it like this in a reducer or setState:

type Item = { id: string; name: string };

function addMaybeBonus(list: Item[], hasCoupon: boolean): Item[] {
  const bonus = { id: "bonus-1", name: "Bonus" } as const;
  return [...list, ...(hasCoupon ? [bonus] : [])];
}

const next = addMaybeBonus([{ id: "base-1", name: "Base" }], true);
// next: [{ id: "base-1", name: "Base" }, { id: "bonus-1", name: "Bonus" }]

Short and safe. React stays happy.


6) API payloads: build, then clean

When I build payloads, I add parts based on flags. Then I remove empty bits.

type Line = { sku: string; qty: number };
type Payload = { lines: Line[] };

function makePayload(hasGift: boolean): Payload {
  const base: Line = { sku: "A-100", qty: 1 };
  const gift: Line | undefined = hasGift ? { sku: "GIFT", qty: 1 } : undefined;

  const isDefined = <T>(x: T | undefined | null): x is T => x != null;

  const lines = [base, gift].filter(isDefined);
  return { lines };
}

console.log(makePayload(true).lines);
// [{ sku: "A-100", qty: 1 }, { sku: "GIFT", qty: 1 }]

This keeps the type of lines clean: it’s Line[], not (Line | undefined)[].


7) Guard the shape with satisfies (nice for TS 4.9+)

If I want Typescript to check the object shape at build time, I’ll use satisfies. It’s a neat little check.

type Item = { id: string; name: string };

const maybeBonus = {
  id: "bonus-1",
  name: "Bonus",
} satisfies Item;

// Later
const items: Item[] = [
  { id: "base-1", title: "Base" },
  ...(true ? [maybeBonus] : []),
];

It won’t change runtime, but it helps me catch mistakes early. You can see how the language team continues to refine these type-safety helpers in the official TypeScript: Documentation – TypeScript 5.5 release notes.


What went wrong before (so you don’t repeat it)

  • I left undefined in arrays. Components crashed or rendered “blank” holes.
  • I used filter(Boolean) and lost real values like 0. Oops.
  • I called cond && arr.push(obj) and forgot it returns a number. That made a weird bug in a chain.