Skip to content

Commit

Permalink
Merge pull request #24 from cesarParra/improved-error-handling
Browse files Browse the repository at this point in the history
Improved error handling
  • Loading branch information
cesarParra authored Dec 6, 2024
2 parents 8144196 + 33ffce1 commit b43f1e7
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 2 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,33 @@ $effect(() => console.log(counter.value));

> ❗ DO NOT use `$effect` to update the signal value, as it will create an infinite loop.
## Peeking at the signal value

If the rare case that you have an effect that needs to read of a signal without subscribing to it, you can
use the signal's `peek` function to read the value.

```javascript
import { $signal, $effect } from "c/signals";

const counter = $signal(0);

$effect(() => console.log(counter.peek()));
```

This can be useful when you need to update the value of a signal based on its current value, but you want
to avoid causing a circular dependency.

```javascript
const counter = $signal(0);
$effect(() => {
// Without peeking, this kind of operation would cause a circular dependency.
counter.value = counter.peek() + 1;
});
```

Note that you should use this feature sparingly, as it can lead to bugs that are hard to track down.
The preferred way of reading a signal is through the `signal.value`.

## Error Handling

When unhandled errors occur in a `computed` or `effect`, by default, the error will be logged to the console through
Expand Down
27 changes: 27 additions & 0 deletions src/lwc/signals/__tests__/computed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,31 @@ describe("computed values", () => {

expect(computed.value).toBe("fallback");
});

test("allows for custom error handlers to return the previous value", () => {
const signal = $signal(0);
function customErrorHandlerFn(_error: unknown, previousValue: number | undefined) {
return previousValue;
}

const computed = $computed(() => {
if (signal.value === 2) {
throw new Error("test");
}

return signal.value;
}, {
errorHandler: customErrorHandlerFn
});

expect(computed.value).toBe(0);

signal.value = 1;

expect(computed.value).toBe(1)

signal.value = 2;

expect(computed.value).toBe(1);
});
});
10 changes: 10 additions & 0 deletions src/lwc/signals/__tests__/effect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,14 @@ describe("effects", () => {

expect(customErrorHandlerFn).toHaveBeenCalled();
});

test("can change and read a signal value without causing a cycle by peeking at it", () => {
const counter = $signal(0);
$effect(() => {
// Without peeking, this kind of operation would cause a circular dependency.
counter.value = counter.peek() + 1;
});

expect(counter.value).toBe(1);
});
});
8 changes: 8 additions & 0 deletions src/lwc/signals/__tests__/signals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ describe("signals", () => {
const signal = $signal(0);
expect(signal.brand).toBe(Symbol.for("lwc-signals"));
});

test("allow for peeking the value without triggering a reactivity", () => {
const signal = $signal(0);
const spy = jest.fn(() => signal.value);
const value = signal.peek();
expect(spy).not.toHaveBeenCalled();
expect(value).toBe(0);
});
});


9 changes: 7 additions & 2 deletions src/lwc/signals/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Signal<T> = {
set value(newValue: T);
readOnly: ReadOnlySignal<T>;
brand: symbol;
peek(): T;
};

const context: VoidFunction[] = [];
Expand Down Expand Up @@ -105,7 +106,7 @@ function handleEffectError(error: unknown, props: EffectProps) {
type ComputedFunction<T> = () => T;
type ComputedProps<T> = {
identifier: string | null;
errorHandler?: (error: unknown) => T | undefined;
errorHandler?: (error: unknown, previousValue: T | undefined) => T | undefined;
};

/**
Expand Down Expand Up @@ -139,7 +140,8 @@ function $computed<T>(
try {
computedSignal.value = fn();
} catch (error) {
computedSignal.value = props.errorHandler(error);
const previousValue = computedSignal.peek();
computedSignal.value = props.errorHandler(error, previousValue);
}
} else {
// Otherwise, the error handling is done in the $effect
Expand Down Expand Up @@ -306,6 +308,9 @@ function $signal<T>(
get value() {
return getter();
}
},
peek() {
return _storageOption.get();
}
};

Expand Down

0 comments on commit b43f1e7

Please sign in to comment.