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 находится уровнем левее.