TypeError: Cannot set properties of undefined означает, что JavaScript попытался записать свойство в значение undefined. В строке order.customer.name = 'Анна' целью записи служит name, но сначала движок должен получить основу слева от последней точки — order.customer. Если там undefined, свойства name ещё не у чего менять.
Сообщение в V8 обычно уточняет имя: Cannot set properties of undefined (setting 'name'). Скобки называют записываемое свойство, а искать нужно отсутствующее значение перед ним. Firefox и Safari формулируют ту же ошибку иначе, поэтому ориентируйся на тип ошибки, строку и операцию, а не на дословный текст. Не создавай name в случайном месте: сначала выясни, почему нет customer.
Записывать некуда: отсутствует основа присваивания
name, но проверять надо его основу: значение выражения order.customer.Как найти undefined по стеку и консоли
Открой полный стек ошибки и перейди к первому кадру из своего кода. В упавшей строке найди присваивание и отдели последнюю операцию доступа:
order.customer.name = 'Анна'
// основа: order.customer
// записываемое свойство: name
Сохрани основу во временную переменную непосредственно перед ошибкой. Так консоль покажет значение в нужный момент, а не после следующего обновления состояния:
const customer = order.customer
console.log({ customer, order })
customer.name = 'Анна'
Если в строке несколько индексов и точек, раскладывай её слева направо. Например, у state.orders[activeIndex].customer.name есть три промежуточных значения: state.orders, state.orders[activeIndex] и .customer. Проверяй их по одному, пока впервые не получите undefined.
Не ограничивайся проверкой typeof value: выражение typeof missing === 'undefined' подтверждает симптом, но не объясняет источник. Полезнее вывести значение вместе с индексом, идентификатором поиска или этапом загрузки:
const order = orders[activeIndex]
console.log({ activeIndex, ordersLength: orders.length, order })
order.status = 'paid'
После этого иди назад по данным: где массив наполняется, кто выбирает activeIndex, что возвращает поиск и успел ли завершиться асинхронный запрос. Первая точка, где обязательное значение стало undefined, обычно и есть место исправления.
Чем запись отличается от чтения и деструктуризации
В profile.name = 'Анна' значение profile используют как основу записи. Если оно равно undefined, V8 сообщает Cannot set properties of undefined. В console.log(profile.name) тот же отсутствующий объект используют для чтения, поэтому сообщение будет Cannot read properties of undefined. Диагностика чтения и безопасные проверки разобраны отдельно в Cannot read properties of undefined.
Деструктуризация выглядит иначе: const { name } = profile извлекает поля в новые переменные. При profile === undefined она тоже выбрасывает TypeError, но у неё другие исправления и риски значений по умолчанию. Они собраны в разборе деструктуризации.
Optional chaining подходит для необязательного чтения или вызова, но не для цели присваивания. Такая запись синтаксически недопустима:
order.customer?.name = 'Анна'
// SyntaxError: optional chaining нельзя использовать как цель присваивания
Если запись обязательна, перед ней должен существовать объект. Если операция необязательна, условие должно явно решить, пропустить её или сообщить об отсутствии данных.
Вложенный объект не был создан
Литерал создаёт только перечисленные уровни. Пустой settings не содержит theme, поэтому записать settings.theme.mode нельзя:
const settings = {}
settings.theme.mode = 'dark'
// TypeError: Cannot set properties of undefined (setting 'mode')
Для обязательной структуры создай её там, где объект появляется, а не перед каждым использованием:
function createSettings() {
return {
theme: {
mode: 'system',
},
}
}
const settings = createSettings()
settings.theme.mode = 'dark'
У данных из API границей может быть функция разбора ответа. Она либо возвращает полную структуру, либо сразу отклоняет неверный ответ:
function parseOrder(payload) {
if (
typeof payload !== 'object' ||
payload === null ||
Array.isArray(payload) ||
typeof payload.customer !== 'object' ||
payload.customer === null ||
Array.isArray(payload.customer)
) {
throw new TypeError('В ответе нет обязательного customer')
}
return payload
}
Так ошибка остаётся рядом с источником данных. Если молча подставить {} глубоко в интерфейсе, программа продолжит работу с частично неверным заказом и упадёт позже в менее очевидном месте.
Разреженный массив и неверный индекс
Длина массива не гарантирует, что по каждому индексу лежит объект. new Array(3) создаёт три пустые позиции; чтение любой из них даёт undefined:
const rows = new Array(3)
rows[1].status = 'ready'
// TypeError: Cannot set properties of undefined (setting 'status')
Если по контракту нужен объект в каждой позиции, создай отдельный объект для каждого элемента:
const rows = Array.from({ length: 3 }, () => ({ status: 'new' }))
rows[1].status = 'ready'
Не заменяй этот код на Array(3).fill({ status: 'new' }): fill положит одну и ту же ссылку во все позиции, и изменение одной строки изменит остальные. Когда индекс приходит извне, проверь его до записи:
function markReady(rows, index) {
const row = rows[index]
if (row === undefined) {
throw new RangeError(`Строка ${index} не существует`)
}
row.status = 'ready'
}
Такая проверка полезнее, чем автоматическое создание rows[index] = {}: неверный индекс может быть самостоятельной ошибкой в пагинации или выборе элемента.
Поиск ничего не вернул
Array.prototype.find() возвращает undefined, если элемент не найден. Следующее присваивание падает уже не в поиске, а на строке записи:
const users = [{ id: 1, role: 'user' }]
const user = users.find((item) => item.id === 7)
user.role = 'admin'
// TypeError: Cannot set properties of undefined (setting 'role')
Выбери поведение по смыслу операции. Если отсутствие допустимо, заверши ветку до записи. Если пользователь обязан существовать, выбрось предметную ошибку рядом с поиском:
function requireUser(users, userId) {
const user = users.find((item) => item.id === userId)
if (user === undefined) {
throw new Error(`Пользователь ${userId} не найден`)
}
return user
}
requireUser(users, 7).role = 'admin'
Тот же принцип работает для словарей, кэшей и результатов поиска по идентификатору: проверяй контракт в месте, где значение может отсутствовать.
Асинхронные данные и состояние компонента
Асинхронный результат какое-то время отсутствует. Если обработчик пытается изменить вложенное поле до завершения загрузки, объект под ним может быть undefined:
let state = { order: undefined }
async function loadOrder() {
state.order = await fetchOrder()
}
function renameCustomer(name) {
state.order.customer.name = name
}
Здесь пустой объект не всегда правильное начальное значение. Состояния «заказ ещё загружается» и «загружен заказ без customer» означают разное. До готовности данных отключи действие или явно останови обработчик, а ответ проверь на границе:
function renameCustomer(name) {
if (state.order === undefined) {
throw new Error('Заказ ещё не загружен')
}
state.order.customer.name = name
}
В компонентных библиотеках учитывай и их модель обновления состояния. Прямое изменение вложенного объекта может не запустить перерисовку даже после устранения undefined; используй штатный механизм обновления конкретного фреймворка.
DOM чаще возвращает null, но причина та же
document.getElementById() возвращает null, когда элемента с таким id нет. Поэтому похожая строка обычно даёт Cannot set properties of null, а не undefined:
const status = document.getElementById('order-status')
status.textContent = 'Готово'
Проверь селектор, момент выполнения и условный рендеринг. Если элемент обязателен, сообщи об ошибке сразу после поиска:
const status = document.getElementById('order-status')
if (status === null) {
throw new Error('Не найден элемент #order-status')
}
status.textContent = 'Готово'
Запуск кода после разбора разметки исправляет только ошибку порядка выполнения. Он не поможет, если id написан неверно или компонент ещё не отрисовал ветку с элементом.
Когда уместен оператор ??=
Оператор ??= присваивает правую часть, только если текущее значение равно null или undefined. Он удобен для ленивого создания необязательного контейнера:
function addLabel(options, label) {
options.labels ??= []
options.labels.push(label)
}
Если по контракту labels разрешено не передавать, такая инициализация выражает правило прямо. Она также сохраняет допустимые ложные значения вроде false, 0 и пустой строки, в отличие от ||=.
Для нескольких необязательных уровней каждый уровень создают отдельно:
settings.sections ??= {}
settings.sections.hero ??= {}
settings.sections.hero.title = 'Практика JavaScript'
Но ??= не должен чинить обязательное поле ответа, конфигурации или состояния. Такой код скрывает нарушение схемы:
order.customer ??= {}
order.customer.name = 'Анна'
Если customer обязан прийти с сервера, после подстановки пустого объекта исчезнет ранний сигнал о повреждённых данных. Проверь customer в parseOrder и останови операцию. Применяй ??= только там, где отсутствие значения предусмотрено контрактом и создание значения по умолчанию сохраняет смысл данных.
Короткий порядок исправления
- Найди первый кадр своего кода в стеке и строку с присваиванием.
- Отдели основу слева от последней точки или пары скобок и выведи её непосредственно перед сбоем.
- Проследи, откуда пришёл
undefined: незаполненный уровень, пустая позиция массива, неверный индекс, неудачный поиск или незавершённая загрузка. - Исправь границу: создай обязательную структуру при конструировании, проверь внешний ответ либо обработай законное отсутствие.
- Используй
??=только для действительно необязательного контейнера; не маскируй им нарушенный контракт.
Главный ориентир — не свойство из скобок сообщения, а значение перед ним. Cannot set properties of undefined (setting 'name') говорит, что записывать name некуда; источник undefined находится уровнем левее.