# Foolproofing StateField

**URL:** <https://discuss.codemirror.net/t/foolproofing-statefield/9694>\
**Category:** v6\
**Created:** [March 15, 2026, 6:06pm UTC](https://discuss.codemirror.net/t/foolproofing-statefield/9694 "2026-03-15T18:06:57Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![push-f](https://discuss.codemirror.net/letter_avatar_proxy/v4/letter/p/ce73a5/32.png) [@push-f](https://discuss.codemirror.net/u/push-f)\
**Post date:** [March 15, 2026, 6:06pm UTC](https://discuss.codemirror.net/t/foolproofing-statefield/9694/1 "2026-03-15T18:06:57Z")

</div>

I had naively implemented the `update` of my `StateField` as:

```ts
    update(foos: Foo[], tr: Transaction): Foo[] {
      for (const effect of tr.effects) {
        if (effect.is(pushFoo)) foos.push(effect.value);
        if (effect.is(popFoo)) foos.pop();
      }
      return foos;
    },

```

The mistake being that you shouldn’t mutate the given value but return a new one. This only had (poorly) worked because I had left `compare⁠` unimplemented, which defaults to `===`.

I think it would be nice if the signature:

```
update: (value: Value, transaction: Transaction) => Value;

```

would be changed to:

```
update: (value: Readonly<Value>, transaction: Transaction) => Readonly<Value>;

```

to prevent such stupid mistakes. Or alternatively the `compare` default implementation could be removed or codemirror could log an error if `create` returns an array or an object and `compare` isn’t implemented since in that case you’ll definitely want to implement it.

---

<div class="post-metadata">

**Author:** ![push-f](https://discuss.codemirror.net/letter_avatar_proxy/v4/letter/p/ce73a5/32.png) [@push-f](https://discuss.codemirror.net/u/push-f)\
**Post date:** [March 15, 2026, 6:11pm UTC](https://discuss.codemirror.net/t/foolproofing-statefield/9694/2 "2026-03-15T18:11:13Z")

</div>

Also just occurred to me: `StateFieldSpec` could be made conditional to require `compare` for records and arrays:

```ts
type StateFieldSpec<Value> = Value extends Record<string, unknown> | unknown[]
  ? { compare: (a: Value, b: Value) => boolean }
  : { compare?: (a: Value, b: Value) => boolean };

// ok
const a: StateFieldSpec<number> = {};

// type error
const b: StateFieldSpec<{ name: string }> = {};

// type error
const c: StateFieldSpec<string[]> = {};

```

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/marijn/32/136_2.png) [@marijn](https://discuss.codemirror.net/u/marijn)\
**Post date:** [March 16, 2026, 7:15am UTC](https://discuss.codemirror.net/t/foolproofing-statefield/9694/3 "2026-03-16T07:15:14Z")

</div>

Unfortunately, TypeScript has no real way of annotating that a type should be immutable (you’ll still have the same issue with nested objects even if you use `Readonly`, and randomly adding it to method signatures feels messy when what you actually want to encode is a constraint on the type given for `Value` itself) So I think we’ll just have to continue to rely on documentation here, and hope people read it.

> [@push-f](#):
>
> Also just occurred to me: `StateFieldSpec` could be made conditional to require `compare` for records and arrays:

It’s often perfectly reasonable to rely on identity compare for such types. Since they are immutable, you’ll generally only replace them with a new value when something changed.
