immutable-js/immutable-js · error · TypeError

Cannot update immutable value without .set() method: {}

Error message

Cannot update immutable value without .set() method: {}

What it means

updateIn walks a keyPath level by level. At some level the existing value exists (wasNotSet is false) but is not a data structure — you cannot index into a number/string/boolean to continue the path. The message includes the path prefix that failed.

Source

Thrown at src/functional/set.ts:54

): C;
export function set<K, V, C extends Collection<K, V> | { [key: string]: V }>(
  collection: C,
  key: K | string,
  value: V
): C {
  if (isProtoKey(key)) {
    return collection;
  }

  if (!isDataStructure(collection)) {
    throw new TypeError(
      'Cannot update non-data-structure value: ' + collection
    );
  }
  if (isImmutable(collection)) {
    // @ts-expect-error weird "set" here,
    if (!collection.set) {
      throw new TypeError(
        'Cannot update immutable value without .set() method: ' + collection
      );
    }
    // @ts-expect-error weird "set" here,
    return collection.set(key, value);
  }
  // @ts-expect-error mix of key and string here. Probably need a more fine type here
  if (hasOwnProperty.call(collection, key) && value === collection[key]) {
    return collection;
  }
  const collectionCopy = shallowCopy(collection);
  // @ts-expect-error mix of key and string here. Probably need a more fine type here
  collectionCopy[key] = value;
  return collectionCopy;
}

View on GitHub (pinned to 59fdaae676)

Solutions

  1. Fix the keyPath to stop at, or go around, the scalar value
  2. Ensure intermediate values are Maps/Objects: initialize defaults as Map()/{} not scalars
  3. Use updateIn's notSetValue argument or a custom updater that replaces non-structures: updateIn(keys, v => isDataStructure(v) ? v : Map(), then set)

Example fix

// before
Map({ user: { name: 'Ada' } }).updateIn(['user', 'name', 'first'], fn); // name is a string
// after
Map({ user: { name: { first: 'Ada' } } }).updateIn(['user', 'name', 'first'], fn);
Defensive patterns

Strategy: type-guard

Validate before calling

import { isDataStructure } from 'immutable/dist/predicates';
// or: const isDS = v => isImmutable(v) || Array.isArray(v) || (v && typeof v === 'object');
function pathTraversable(state, keys) {
  let cur = state;
  for (const k of keys.slice(0, -1)) {
    if (cur == null) return true; // will be created
    if (!isDS(cur)) return false;
    cur = cur.get ? cur.get(k) : cur[k];
  }
  return true;
}

Type guard

const isDataStructure = (v: unknown): v is object => isImmutable(v as any) || Array.isArray(v) || (!!v && typeof v === 'object');

Try / catch

try { state.updateIn(path, fn); } catch (e) { if (/non-data-structure/.test(e.message)) { /* normalize leaf to Map and retry once */ } else throw e; }

Prevention

When it happens

Trigger: Map({ a: 5 }).updateIn(['a','b'], () => 1); state.updateIn(['user','name','first'], f) where user.name is a string; List path hitting a scalar element.

Common situations: State shape drift: a field that used to be a nested object became a scalar after a refactor or API change; key paths built dynamically with an extra segment; optional fields that default to '' or 0 instead of Maps.

Related errors


AI-assisted analysis of immutable-js/immutable-js@59fdaae676 (2026-08-27). Data as JSON: /api/errors/3d32ba0139f18ee5. Report an issue: GitHub.