toeverything/AFFiNE · error · BlockSuiteError
ReactiveProxyError
ReactiveProxyError
Error message
key cannot be a symbol
What it means
ReactiveYArray wraps a plain JS array and forwards writes into a Yjs Y.Array inside a doc transaction. Yjs keys are string-addressable only, so the proxy's `set` trap hard-rejects any write whose property key is a symbol before any data is touched. This is an invariant guard, not a data error: nothing is partially written when it fires.
Solutions
- Store symbol-keyed metadata in a side WeakMap keyed by the proxy instead of on the proxy itself
- Use plain string keys for any property you intentionally attach to the array
- Hand third-party code a plain copy (`[...arrProxy]` / `structuredClone(toJSON(arrProxy))`) so it stamps symbols on the copy, not the reactive proxy
- If the writer cannot be controlled, catch BlockSuiteError with code ReactiveProxyError and fall back to non-reactive storage
Example fix
// before
yArrayProxy[Symbol('meta')] = { touchedAt: Date.now() };
// after
const meta = new WeakKeyMap... // use a side table
const meta = new WeakMap<object, unknown>();
meta.set(yArrayProxy, { touchedAt: Date.now() }); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof key !== 'string') {
sideTable.set(arrProxy, value); // never write symbol keys to the proxy
} else {
arrProxy[key] = value;
} Type guard
const isStringKey = (key: PropertyKey): key is string => typeof key === 'string';
Try / catch
try {
arrProxy[key] = value;
} catch (e) {
if (e instanceof BlockSuiteError && e.code === ErrorCode.ReactiveProxyError && e.message.includes('symbol')) {
sideTable.set(arrProxy, value); // graceful fallback
} else throw e;
} Prevention
- Never attach symbol-keyed properties to reactive arrays; use a side WeakMap for metadata
- Pass plain copies to third-party libraries that decorate objects with symbols
- Iterate Object.keys(), not Reflect.ownKeys(), when writing or deleting dynamically
When it happens
Trigger: `arrProxy[Symbol('meta')] = value`; `Object.assign(reactiveArray, objWithSymbolKeys)`; a dependency (Vue reactivity flags, immer, class instances carrying symbol internals) stamping symbol-keyed metadata onto the reactive array; overriding `arrProxy[Symbol.iterator]`.
Common situations: Passing a BlockSuite reactive array into a reactivity/serialization library that decorates objects with symbol keys; using symbol keys as collision-free metadata on shared state; HMR setups that reuse proxies with framework internals that expect symbols.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/2835ee65fb29a897.
Report an issue: GitHub.
Appendix: source
Thrown at blocksuite/framework/store/src/reactive/proxy.ts:52
this._updateWithSkip(() => {
this._source.splice(retain, 0, ...proxyList);
});
retain += change.insert.length;
}
});
});
};
protected _getProxy = () => {
return new Proxy(this._source, {
has: (target, p) => {
return Reflect.has(target, p);
},
set: (target, p, value, receiver) => {
if (typeof p !== 'string') {
throw new BlockSuiteError(
ErrorCode.ReactiveProxyError,
'key cannot be a symbol'
);
}
const index = Number(p);
if (this._skipNext || Number.isNaN(index)) {
return Reflect.set(target, p, value, receiver);
}
if (this._stashed.has(index)) {
const result = Reflect.set(target, p, value, receiver);
this._options.onChange?.(this._proxy, true);
return result;
}
const reactive = proxies.get(this._ySource);
if (!reactive) {View on GitHub (pinned to b4c8548c09)