Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/ObservableObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,16 @@ export function flushPending() {
const proxyHandler: ProxyHandler<any> = {
get(node: NodeInfo, p: any, receiver: any) {
if (p === symbolToPrimitive) {
throw new Error(
process.env.NODE_ENV === 'development'
? '[legend-state] observable should not be used as a primitive. You may have forgotten to use .get() or .peek() to get the value of the observable.'
: '[legend-state] observable is not a primitive.',
);
// Return a toPrimitive function instead of throwing so that external code
// (e.g. React 19's dev-mode logComponentRender) can safely coerce observables
// without crashing. A dev-mode warning is still emitted to help catch
// accidental primitive usage in user code.
if (process.env.NODE_ENV === 'development') {
console.warn(
'[legend-state] observable is being converted to a primitive. You may have forgotten to use .get() or .peek() to get the value of the observable.',
);
}
return (hint: string) => (hint === 'number' ? NaN : '[Observable]');
}
if (p === symbolGetNode) {
return node;
Expand Down
20 changes: 15 additions & 5 deletions tests/tests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3493,16 +3493,26 @@ describe('_', () => {
});
});
describe('Built-in functions', () => {
test('Adding observables should throw', () => {
test('Adding observables should warn and return NaN', () => {
const obs = observable({ x: 0, y: 0 });

const x = obs.x;
const y = obs.y;

expect(() => {
// @ts-expect-error Testing error
x + y;
}).toThrowError(/observable is not a primitive/);
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});

// @ts-expect-error Testing primitive coercion
const result = x + y;

// Should produce NaN since both observables coerce to NaN for numeric hint
expect(result).toBeNaN();

// Should warn in development mode
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('observable is being converted to a primitive'),
);

warnSpy.mockRestore();
});
});
describe('setAtPath', () => {
Expand Down