Push Is Not A Function | Fix The Array Type Fast

The “push is not a function” error means .push() was called on a value that isn’t an array, so you need to fix the value’s type.

You’ll usually see this error right after you add one more item to a list. The “array” you thought you had is a string, an object, null, or a list-like value that only acts like an array.

This guide shows a clean way to spot what you’re pushing into, why it changed shape, and how to lock it down so it won’t regress next week.

What .push() Works On And What It Refuses

Array.prototype.push() exists on real arrays. It appends one or more values. If the left side is not an array, JavaScript can’t find push on that value, and you get the error.

A quick check is printing the value right before the call and checking its type. In Chrome DevTools, a real array shows as [] with an Array(n) label. A string shows quotes. An object shows braces.

Don’t trust what you “meant” to store in the variable. Trust what is there at runtime. If the value comes from user input, a URL query, a form field, or a network response, it can arrive as text even when it looks like a list on the page.

If you’re dealing with nested data, check the whole chain. A common case is data.items being an array, but data being null during the first render. The error message points at push, yet the fix is guarding the parent object.

  • Log The Valueconsole.log(list) right before list.push(...) so you see the real runtime shape.
  • Check Array.isArrayArray.isArray(list) is the simplest truth test.
  • Check The Assignment — search for every place the variable is set, not just where it’s used.

Push Is Not A Function Errors That Come From Data Shape Changes

Most cases come down to one theme: a variable starts life as an array, then gets replaced with a non-array value. That replacement can be obvious, or it can hide inside a helper, a fetch call, or state updates.

It Was Never Initialized As An Array

A common slip is starting with {}, "", or leaving a variable undefined. Then later you treat it like a list.

This happens a lot with conditional code. One branch sets an array. Another branch sets a single item, a string, or nothing at all. The variable name stays the same, so the later code feels safe, until that other branch runs.

// Bad
let items;
items.push("a");
// Good
let items = [];
items.push("a");

It Became An Object Instead Of An Array

APIs often return objects that contain an array under a field, not the array itself. If you push on the wrapper object, you’ll hit the error.

// Response shape: { data: [...] }
const res = await fetch("/api/things").then(r => r.json());
res.push("x"); // error
res.data.push("x"); // ok

It Turned Into A String After JSON Handling

It Came From split(), map(), Or filter() On The Wrong Value

Many array-producing helpers live on strings and arrays. If the source is not what you think, the result changes shape too. A string split gives an array. A missing delimiter can give a one-item array. A missing source can give you undefined, and then the next line tries to push.

  • Guard The Source — check the value you call split, map, or filter on before chaining.
  • Keep Return Types Stable — if a helper returns an array on one path, return an array on every path.
  • Use Array.from On Iterables — iterables like Map keys need conversion before array methods.

Strings can look like arrays when they contain JSON text. If you forget to parse, you’ll be pushing on a string.

  • Check For Quotes — if DevTools shows a quoted value like "[1,2]", it’s still a string.
  • Parse OnceJSON.parse(text) gives you the real array or object.
  • Guard Parse Errors — wrap parsing in try/catch so bad data doesn’t cascade.

It Is A NodeList Or HTMLCollection

DOM queries can return list-like objects. They have length and indexed access, but they are not arrays, so they do not have push.

const nodes = document.querySelectorAll("li");
nodes.push(document.createElement("li")); // error
const nodes = Array.from(document.querySelectorAll("li"));
nodes.push(document.createElement("li")); // ok

It Is A Typed Array Or Other Array-Like Value

Typed arrays like Uint8Array have fixed length. They don’t have push. Convert to a normal array when you need to grow the list.

What You Have How To Turn It Into An Array Notes
NodeList / HTMLCollection Array.from(x) Keeps order; safe for DOM lists
Typed array Array.from(x) Copies values; new array is growable
Object values Object.values(x) Only values; keys are dropped
Maybe-one-item value [].concat(x ?? []) Handles nullish; keeps arrays as-is

Fixes That Work In Real Code Without Weird Side Effects

Once you know what the value is, the fix is usually short. The trick is choosing a fix that matches the intent of your data flow, not just silencing the error.

Initialize With A Safe Default

If you store the list on an object, watch out for shared defaults. A default like const defaults = { tags: [] } can leak the same array into many places if you reuse it. Create a new array per instance, or clone the default when you apply it.

If the value can be missing, give it a default array at the boundary where it enters your function or component.

function addTag(state, tag) {
  const tags = state.tags ?? [];
  tags.push(tag);
  return { ...state, tags };
}

Normalize Inputs At The Edge

APIs, forms, and query params are messy. Normalize once at the edge, then keep the rest of your code strict.

  • Coerce Single To Many — if you accept one item or many, convert to an array right away.
  • Map Unknown To Empty — treat null and undefined as “no items.”
  • Validate Early — throw a clear error when the payload shape is wrong.

Don’t Mutate When You’re Working With State

In React, Vue, and many state systems, mutation can be the hidden reason your “array” stops being what you expect. A stale reference can get replaced, or a shallow copy can pull in a non-array.

// Safer: create a new array
setItems(prev => [...prev, newItem]);

Use Spread Or Concat When You Want A New Array

If you’re in code that should stay immutable, avoid push entirely. You get the same result without mutation.

const next = [...items, item];
const next2 = items.concat(item);

Handle “Array Or Object” Responses Explicitly

Some endpoints return an array on success and an object on error. That’s a trap. Check and branch.

const payload = await r.json();
if (!Array.isArray(payload)) {
  throw new Error("Expected an array payload");
}
payload.push(extra);

Debug Steps That Find The Root Cause Fast

If you only patch the line with push, the bug may pop up again in a different spot. A tight debug pass makes sure you fix the source.

  1. Reproduce The Exact Case — copy the input that triggers the error, then run the same path in a clean reload.
  2. Set A Breakpoint — pause on the line before push, then inspect the variable in the Scope panel.
  3. Trace Back Assignments — in your editor, search for every = assignment to that variable name.
  4. Log Type And Source — log typeof, Array.isArray, and the value, plus a tag that tells you which code path ran.
  5. Check Async Timing — if the value is set by a fetch, verify the code waits for the result before appending.
  6. Lock The Shape — add a runtime guard or TypeScript type so the wrong shape fails early.

If you’re using TypeScript, pay attention to unions like (Thing[] | Thing) or (Thing[] | undefined). Those types are telling you the runtime can vary. Fix by normalizing, not by casting.

Framework And Backend Patterns That Trigger The Bug

This error shows up across stacks. The details differ, but the pattern stays the same: a value that “looks like a list” is not a normal array.

React State That Starts As null

If you start state at null and later treat it as an array, you’ll crash on first use. Default to an empty array unless you truly need the tri-state.

const [items, setItems] = useState([]);

Vue Refs And Reactive Wrappers

With refs, the array is often under .value. Pushing on the wrapper object triggers the error.

const items = ref([]);
items.value.push("x");

Express req.body That Isn’t Parsed

If your server does not parse JSON, req.body can be a string. Parse and validate before using array methods.

Next.js Or Remix Loaders Returning The Wrong Shape

Server loaders often return JSON objects that wrap your list. If the client code expects an array, it will try to push on the wrapper. Keep the loader contract plain: return { items: [] } and always read items, or return the array and keep it that way everywhere.

Svelte Stores And Writable Values

With stores, the store itself is not the array. You need the stored value. Read the store, update the array value, then set it back. If you push on the store object, you’ll get the same error with different line numbers.

MongoDB And ORM Results

Some ORMs return cursor-like objects, not plain arrays. Convert with the method your library provides, then treat the result as an array.

  • Convert Cursors — in many Mongo drivers, call toArray() on a cursor before using array methods.
  • Check Lean Results — some “lean” modes change shapes; confirm the returned type in docs.
  • Avoid Reusing Shared Objects — don’t stash mutable results in a module-level variable.

Prevent It From Coming Back

Once you fix the immediate crash, spend a few minutes making the code harder to misuse. That time pays off fast, since “push is not a function” often shows up again after a refactor.

Use Guards Where Data Enters Your Code

Runtime checks are fine when input comes from outside your control. They keep failures loud and local.

function mustBeArray(value, name) {
  if (!Array.isArray(value)) {
    throw new TypeError(`${name} must be an array`);
  }
  return value;
}

Prefer Clear Data Contracts

When you can’t control the input, add a small normalizer. Keep it boring and obvious. It should take anything and return an array.

function asArray(value) {
  if (Array.isArray(value)) return value;
  if (value == null) return [];
  return [value];
}

If you own both sides of an API, return consistent shapes. Always return arrays for list endpoints, even when empty. If you need error details, send them in a separate error response format.

Add A Tiny Test Around The Contract

A small test that asserts “this function returns an array” catches regressions before they ship.

  • Assert The Type — check Array.isArray(result) in unit tests.
  • Test Empty Cases — verify the function returns [] when there are no items.
  • Test Bad Payloads — feed a string or object and confirm it fails with a clear message.

When you see push is not a function, treat it as a helpful alarm. It’s pointing to a data-shape mismatch. Fix the mismatch, then add a small guard so the next person who edits the code can’t trip the same wire.