TS1011Syntax Error
Since TS 2.9

Fix TS1011: Element Access Expression Needs an Argument

Learn why TypeScript throws TS1011 when an array type is written where a value belongs, and how to fix useState(string[]), arr[] = value and empty indexes.

error TS1011: An element access expression should take an argument

What This Error Means

TS1011 is a syntax error: the TypeScript parser found a pair of square brackets with nothing between them in a place where it expected a value. The message is literal — an element access expression like orders[0] must take an argument, and orders[] gives it none.

Empty brackets do have a meaning in TypeScript, but only in type positions. In let tags: string[], the [] turns string into an array type. The moment the same characters appear in an expression position — on the right of =, inside a function call, as a default value — the parser stops reading them as a type and reads them as indexing, which needs an index.

In practice this error nearly always means the same thing: an array type was written where a value belongs. The compiler reports it during parsing, before any type checking, so no flag turns it on or off and no tsconfig setting makes it go away.

// The general shape of the error:
let tags = string[]
//               ~~
// Error: An element access expression should take an argument.

The specific wording arrived in TypeScript 2.9. On older compilers the exact same slip was reported as TS1109 Expression expected, which is why search results for these snippets mix the two codes.

Common Causes

1. Missing Colon — the Type Became the Initializer

The most common cause by far. The colon that turns string[] into an annotation is missing, so the array type lands on the right-hand side of the assignment and becomes an expression.

// ❌ Broken
let tags = string[]
//               ~~ Error: An element access expression should take an argument.
// ✅ Fixed — annotate the variable, initialize it with a value
let tags: string[] = []
tags.push("typescript")

Read the fixed line out loud and the split becomes obvious: everything after : and before = is the type, everything after = is the value. An array type never appears on the value side.

2. An Array Type Passed as a Function Argument

Generic functions take types in angle brackets and values in parentheses. Mixing the two is especially easy with React's useState, where the initial value and the state type are so closely related.

// ❌ Broken
import { useState } from "react"
 
export function TagEditor() {
  const [tags, setTags] = useState(string[])
  //                                     ~~ Error: An element access expression should take an argument.
  return <div>{tags.length}</div>
}
// ✅ Fixed — the type goes in angle brackets, the initial value in parentheses
import { useState } from "react"
 
export function TagEditor() {
  const [tags, setTags] = useState<string[]>([])
  return <button onClick={() => setTags([...tags, "new"])}>{tags.length}</button>
}

The same split applies to every generic call: new Map<string, number>(), orders.reduce<string[]>(…, []), JSON.parse(raw) as OrderSummary[].

3. PHP-Style Append With arr[] = value

In PHP, $ids[] = 3 pushes onto an array. JavaScript and TypeScript have no such shorthand, and the parser sees a bracket pair waiting for an index.

// ❌ Broken
const orderIds: number[] = []
orderIds[] = 3
//       ~~ Error: An element access expression should take an argument.
// ✅ Fixed — push, or write the index out
const orderIds: number[] = []
orderIds.push(3)
orderIds[orderIds.length] = 4

Prefer push(). The orderIds[orderIds.length] = 4 form is valid but says the same thing with more moving parts, and it silently creates holes if the index is ever wrong.

4. Empty Brackets When Reading or Defaulting

Two smaller slips produce the identical message: forgetting the index while reading an element, and writing an array type as a parameter's default value.

// ❌ Broken
const orders = ["a-1", "a-2"]
const firstOrder = orders[]
//                       ~~ Error: An element access expression should take an argument.
 
function fetchOrders(statuses = string[]) {
  //                                   ~~ Error: An element access expression should take an argument.
  return statuses
}
// ✅ Fixed — supply the index; annotate the parameter and default it to a value
const orders = ["a-1", "a-2"]
const firstOrder = orders[0]
 
function fetchOrders(statuses: string[] = []) {
  return statuses
}

The parameter case is worth a second look: statuses: string[] = [] carries both pieces of information, the type and the default. statuses = string[] tries to make one token do both jobs and does neither.

How to Fix It

  1. Look at what sits immediately before the empty brackets. If it is a type name — string, number, Order, Record<string, unknown> — you wrote a type where a value belongs. That single observation solves almost every TS1011.

  2. Move the type to where types live. In a declaration it goes after a colon (let tags: string[] = []). In a generic call it goes in angle brackets (useState<string[]>([])). In a parameter it goes after the name, with the default kept separate (statuses: string[] = []).

  3. Replace arr[] = value with arr.push(value). There is no append shorthand in JavaScript. If you are porting PHP, this pattern will show up in every file that builds a list.

  4. Supply the missing index. When the brackets really are an element access, put something in them: a literal (orders[0]), a variable (orders[index]), or a computed key (totals[order.status]).

  5. If the error points inside node_modules, upgrade TypeScript rather than editing the file. A shipped .d.ts that parses as TS1011 is usually using syntax newer than your compiler. Check npx tsc -v against the package's stated requirement, and make sure your editor is using the workspace TypeScript version rather than its bundled one.

  6. Keep the annotation once it compiles. let tags: string[] = [] is not just the fix, it is the better code: it says what the array is meant to hold, and it keeps you clear of the never[] trap in inference positions. A bare [] handed to a generic — useState([]) is the one you will hit — infers never[], and every later push or setter call fails. (A plain let tags = [] is the friendlier case: TypeScript widens it as you go, so tags.push("typescript") makes it string[].)

FAQ

What causes TypeScript error TS1011?

TS1011 fires when the parser reaches a pair of empty square brackets in an expression position. Expressions are the places where a runtime value is expected: the right side of =, an argument inside a call, a default parameter value, a return statement. In those positions [] can only be an element access, and an element access needs an index.

The reason the brackets got there is almost always that an array type was written as a value, as in let tags = string[] instead of let tags: string[] = []. Because the check happens while parsing, no compiler flag affects it — not strict, not noImplicitAny, not the target setting. It also fires in .js files once checkJs is on, usually alongside TS8010 for the type annotations around it.

How do I pass an array type to useState in TypeScript?

Use a type argument: const [tags, setTags] = useState<string[]>([]). The angle brackets tell the generic what it is storing; the parentheses hold the initial value, which for an array state is normally [].

const [tags, setTags] = useState<string[]>([])
const [orders, setOrders] = useState<Order[] | null>(null)

Writing useState(string[]) is the version that raises TS1011, because string[] ends up where the initial value belongs. If you ever leave the type argument off and pass a bare [], TypeScript infers never[] and every later setTags(["a"]) fails — so the explicit type argument is worth keeping even when the error is gone.

Why does arr[] = value not work in TypeScript?

Because that syntax belongs to PHP, not to JavaScript. TypeScript inherits JavaScript's grammar, and JavaScript has never had an append operator — arr[] on its own is an incomplete element access, which is exactly what TS1011 reports.

The direct replacement is arr.push(value). If you need the result of the append, push returns the new length, and const next = [...arr, value] gives you a new array instead of mutating the old one. The literal translation arr[arr.length] = value also works, but it is easy to get wrong when the array is being filtered or spliced in the same loop, so reach for push first.

Related Errors

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Share this reference

Become a TypeScript Pro

Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges