This error occurs when you try to assign a value to an invalid target, such as a function call result, a literal value, or an optional property access. TypeScript requires the left side of an assignment to be a valid lvalue like a variable or object property.
Fixes TS2364
// ❌ Function call result
const result = getUser() = newValue;
// ❌ Literal value
5 = x;
'name' = value;
// ❌ Optional property access
user?.name = 'John';
// ❌ Expression result
(a + b) = value;
// ❌ Const reassignment
const name = 'John';
name = 'Jane'; // Cannot reassign const
// ❌ Assignment in non-declaration context
[a, b] = [1, 2]; // Works in declaration, not as separate statement// ❌ Function call resultconst result = getUser() = newValue;// ❌ Literal value5 = x;'name' = value;// ❌ Optional property accessuser?.name = 'John';// ❌ Expression result(a + b) = value;// ❌ Const reassignmentconst name = 'John';name = 'Jane'; // Cannot reassign const// ❌ Assignment in non-declaration context[a, b] = [1, 2]; // Works in declaration, not as separate statementThe left-hand side of an assignment expression must be a variable or a property access
This TypeScript error indicates that you're attempting to assign a value to something that isn't a valid assignment target (an "lvalue" in compiler terminology). An lvalue is an expression that represents a location in memory where a value can be stored—specifically, a variable or property that can be modified. TypeScript enforces strict rules about what can appear on the left side of an assignment operator. Only the following are valid assignment targets: - A variable name (let, const, or var) - An object property (object.property) - An array element (array[index]) - A destructuring pattern (in declarations only) When you try to assign to something else—like a function's return value, a literal number or string, or an optional property access—TypeScript catches this as a syntax error and reports TS2364.
Look at the line flagged by TypeScript and identify what's on the left side of the = sign.
Common invalid patterns:
// ❌ Function call result
const result = getUser() = newValue;
// ❌ Literal value
5 = x;
'name' = value;
// ❌ Optional property access
user?.name = 'John';
// ❌ Expression result
(a + b) = value;
// ❌ Const reassignment
const name = 'John';
name = 'Jane'; // Cannot reassign const
// ❌ Assignment in non-declaration context
[a, b] = [1, 2]; // Works in declaration, not as separate statementRun TypeScript compiler to see the exact line and column number where the error occurs:
npx tsc --noEmitIf you're trying to capture a function's return value and modify it, assign it to a variable first:
// ❌ Invalid: attempting to assign to function result
getUser() = { name: 'John' };
// ✓ Correct: assign function result to a variable
const user = getUser();
user.name = 'John';
// Or modify in one step if the function returns an object
const user = { ...getUser(), name: 'John' };If you meant to call a setter function:
// ❌ This looks like assignment but isn't valid
setValue(10) = 20;
// ✓ Just call the function
setValue(20);
// Or if you need to pass a different value
const newValue = 20;
setValue(newValue);Optional chaining (?.) is designed for safe property access, not safe assignment. Use an if statement to guard the assignment:
// ❌ Invalid: optional chaining in assignment
employee?.country = 'Germany';
// ✓ Correct: guard with if statement
if (employee) {
employee.country = 'Germany';
}
// Or use nullish coalescing for default values
if (employee != null) {
employee.country = 'Germany';
}
// For simpler cases, use logical AND
employee && (employee.country = 'Germany');When updating nested optional properties:
// ❌ Invalid
user?.profile?.name = 'John';
// ✓ Correct
if (user?.profile) {
user.profile.name = 'John';
}A common mistake is using = (assignment) instead of === (comparison) inside conditionals:
// ❌ This assigns instead of comparing, and = is invalid in if condition
if (method = 'GET') {
// ...
}
// ✓ Use === for comparison
if (method === 'GET') {
// ...
}
// ✓ Use == for loose comparison (generally not recommended)
if (method == 'GET') {
// ...
}Assignment outside conditional context:
Sometimes you want to assign AND check truthiness (valid in JavaScript, but be clear):
// This is valid but confusing
if ((user = getUser())) {
// user is now assigned and truthy
}
// Better: separate concerns
const user = getUser();
if (user) {
// ...
}Destructuring assignment syntax works in declarations but not as standalone reassignments:
// ❌ Invalid: destructuring outside of declaration
[a, b] = [1, 2];
({ x, y } = point);
// ✓ Correct: use 'const', 'let', or 'var' with destructuring
const [a, b] = [1, 2];
let { x, y } = point;
// If you need to reassign existing variables with destructuring:
// Use parentheses to disambiguate from block syntax
({ x, y } = point); // ✓ Valid at statement level with parens
// Or assign to new variables
const { x: newX, y: newY } = point;If trying to reassign a const or readonly property, declare a new variable instead:
// ❌ Invalid: cannot reassign const
const name = 'John';
name = 'Jane';
// ✓ Declare a new variable
const name = 'John';
const updatedName = 'Jane';
// Or use let if reassignment is intentional
let name = 'John';
name = 'Jane'; // ✓ Valid
// For readonly properties:
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: 'John' };
user.id = 2; // ❌ Error: readonly property
user.name = 'Jane'; // ✓ Valid
// Create new object with updated values
const updatedUser = { ...user, name: 'Jane' };After making fixes, verify the error is resolved:
# Check for TypeScript errors without emitting
npx tsc --noEmit
# Or run your build command
npm run build
# In VS Code, the IDE should clear the error immediatelyLook for the specific error TS2364:
npx tsc --noEmit 2>&1 | grep TS2364If the error persists:
1. Check you modified the correct line (line numbers in error message)
2. Ensure you saved the file
3. Restart your TypeScript language server in VS Code: Ctrl+Shift+P > "TypeScript: Restart TS Server"
4. Check for similar patterns elsewhere in the file
### Understanding Lvalues vs Rvalues
In compiler terminology:
- Lvalue ("left value"): An expression that refers to a memory location and can appear on the left side of assignment
- Rvalue ("right value"): An expression that produces a value but doesn't refer to a persistent memory location
let x = 5; // x is lvalue, 5 is rvalue
x = 10; // ✓ Valid: lvalue on left, rvalue on right
10 = x; // ❌ Invalid: rvalue on left### Destructuring Deep Dive
Destructuring works in declarations and in parenthesized statements:
// ✓ Declaration - always works
const [a, b] = [1, 2];
const { x, y } = point;
// ⚠ Statement-level destructuring needs parentheses
// (Otherwise { } is interpreted as a block)
({ x, y } = point);
// ❌ This is parsed as a block, not destructuring
{ x, y } = point;### Optional Chaining Limitations
Optional chaining was intentionally not designed for assignment because it creates ambiguity:
// What should happen if user is null?
user?.name = 'John'; // ❌ Invalid
// The fix is explicit:
if (user) {
user.name = 'John'; // ✓ Clear intent
}### Common TypeScript Assignment Errors
| Error | Cause | Fix |
|-------|-------|-----|
| TS2364 | Invalid lvalue | Use variable or property |
| TS2540 | Assigning to readonly property | Use spreading or new object |
| TS2588 | Cannot assign to const | Declare with let or var |
| TS1005 | Unexpected token | Check syntax of assignment |
### Safer Patterns for Object Updates
When you frequently need to update objects:
// Pattern 1: Object spreading (immutable)
const updated = { ...original, field: newValue };
// Pattern 2: Class setter methods (encapsulated)
class User {
private _name: string;
set name(value: string) {
this._name = value;
}
get name() {
return this._name;
}
}
// Pattern 3: Factory functions
function updateUser(user: User, updates: Partial<User>): User {
return { ...user, ...updates };
}
// Pattern 4: Immer library (for complex updates)
import produce from 'immer';
const updated = produce(user, draft => {
draft.profile.name = 'John';
});