Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor release #23

Merged
merged 28 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
17f7b10
feat: Signals can be identified with a symbol through the "brand" pro…
cesarParra Dec 3, 2024
33c46ea
Additional unit tests
cesarParra Dec 3, 2024
100308e
fix: Computed values that return the unchanged value of a tracked sig…
cesarParra Dec 4, 2024
bda54b0
Improving error handling
cesarParra Dec 5, 2024
8823cf1
console erroring when a computed or effect throws
cesarParra Dec 5, 2024
179a9fd
computed and effects can now be identified by passing a string to the…
cesarParra Dec 5, 2024
01c14c0
effects allow for custom error handlers
cesarParra Dec 5, 2024
b37ebab
computed allow for custom error handlers
cesarParra Dec 5, 2024
acb75f0
error suppression examples
cesarParra Dec 5, 2024
453a429
Documentation updates
cesarParra Dec 5, 2024
a49874a
Including the develop branch in the CI
cesarParra Dec 5, 2024
8144196
feat: Improved error handling (#22)
cesarParra Dec 5, 2024
8998ac6
The computed error handler now sends the computed previous value as a…
cesarParra Dec 6, 2024
8dc1db3
feat: Ability to read a signal value without subscribing to it via th…
cesarParra Dec 6, 2024
33ffce1
Merge branch 'develop' into improved-error-handling
cesarParra Dec 6, 2024
b43f1e7
Merge pull request #24 from cesarParra/improved-error-handling
cesarParra Dec 6, 2024
2d09e5a
Updating example to take into account the previous value
cesarParra Dec 6, 2024
7db87fb
feat: Effects and computed have a default identifier.
cesarParra Dec 6, 2024
9c69493
TSC
cesarParra Dec 6, 2024
f64dc23
Renaming props to options
cesarParra Dec 6, 2024
3edd88d
Renaming error handler callback property
cesarParra Dec 6, 2024
39c58d5
The error handler for effect and computed values now receive the iden…
cesarParra Dec 6, 2024
06dd95b
feat: isASignal function allows to check if an object is a signal
cesarParra Dec 6, 2024
74f2521
feat: Effects and computed have a default identifier.
cesarParra Dec 6, 2024
c6be621
feat: isASignal function allows to check if an object is a signal
cesarParra Dec 6, 2024
ce4a3bc
Merge remote-tracking branch 'origin/develop' into develop
cesarParra Dec 6, 2024
3aad04d
Minor refactorings
cesarParra Dec 7, 2024
f49d0d2
Renaming the examples error handling function to onError
cesarParra Dec 10, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: ["main"]
branches: ["main", "develop"]
pull_request:
branches: ["main"]
branches: ["main", "develop"]
workflow_dispatch:

jobs:
Expand Down
171 changes: 148 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ A simple yet powerful reactive state management solution for Lightning Web Compo
![Version](https://img.shields.io/badge/version-1.1.1-blue)

Inspired by the Signals technology behind SolidJs, Preact, Svelte 5 Runes and the Vue 3 Composition API, LWC Signals is
a
reactive signals for Lightning Web Components that allows you to create reactive data signals
that can be used to share state between components.
a reactive signals implementation for Lightning Web Components.
It allows you to create reactive data signals that can be used to share up-to-date state between components.

It features:

Expand Down Expand Up @@ -161,7 +160,7 @@ export default class Display extends LightningElement {
<img src="./doc-assets/counter-example.gif" alt="Counter Example" />
</p>

### Stacking computed values
#### Stacking computed values

You can also stack computed values to create more complex reactive values that derive from each other

Expand All @@ -176,6 +175,147 @@ export const counterPlusTwo = $computed(() => counterPlusOne.value + 1);

Because `$computed` values return a signal, you can use them as you would use any other signal.

### `$effect`

You can also use the `$effect` function to create a side effect that depends on a signal.

Let's say you want to keep a log of the changes in the `counter` signal.

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

export const counter = $signal(0);

$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
a `console.error` call, and then the error will be rethrown.

If you wish to know which `computed` or `effect` caused the error, you can pass a second argument to the `computed` or
`effect` with a unique identifier.

```javascript
$computed(
() => {
signal.value;
throw new Error("error");
},
{ identifier: "test-identifier" }
);

$effect(
() => {
signal.value;
throw new Error("error");
},
{ identifier: "test-identifier" }
);
```

This value will be used only for debugging purposes, and does not affect the functionality otherwise.

In this example, the test-identifier string will appear as part of the console.error message.

### Custom Error Handlers

Both computed and effect signals can receive a custom `onError` property,
that allows developers to completely override the default functionality that logs and rethrows the error.

#### Effect handlers

For `$effect` handlers, you can pass a function with the following shape:

```typescript
(error: any, options: { identifier: string | symbol }) => void
```

The function will receive the thrown error as the first argument, and an object with the identifier as the second.
It should not return anything.

Example:

```javascript
function customErrorHandlerFn(error) {
// custom logic or logging or rethrowing here
}

$effect(
() => {
throw new Error("test");
},
{
onError: customErrorHandlerFn
}
);
```

#### Computed handlers

For `$computed` handlers, you can pass a function with the following shape:

```typescript
(error: unknown, previousValue: T, options: { identifier: string | symbol }) =>
T | undefined;
```

Where you can return nothing, or a value of type `T`, which should be of the same type as the computed value itself.
This allows you to provide a "fallback" value, that the computed value will receive in case of errors.

As a second argument, you will receive the previous value of the computed signal, which can be useful to provide a
fallback value based on the previous value.

The third argument is an object with the received identifier.

Example

```javascript
function customErrorHandlerFn(error, _previousValue, _options) {
// custom logic or logging or rethrowing here
return "fallback value";
}

$computed(
() => {
throw new Error("test");
},
{
onError: customErrorHandlerFn
}
);
```

## Tracking objects and arrays

By default, for a signal to be reactive it needs to be reassigned. This can be cumbersome when dealing with objects
Expand All @@ -200,8 +340,8 @@ console.log(computedFromObj.value); // 4

## Reacting to multiple signals

You can also use multiple signals in a single `computed` and react to changes in any of them.
This gives you the ability to create complex reactive values that depend on multiple data sources
You can also use multiple signals in a single `computed` or `effect` and react to changes in any of them.
This allows you to create complex reactive values that depend on multiple data sources
without having to track each one independently.

> 👀 You can find the full working code for the following example in the `examples`
Expand Down Expand Up @@ -296,22 +436,6 @@ export default class BusinessCard extends LightningElement {
> ❗ Notice that we are using a property instead of a getter in the `$computed` callback function, because
> we need to reassign the value to `this.contactInfo` to trigger the reactivity, as it is a complex object.

### `$effect`

You can also use the `$effect` function to create a side effect that depends on a signal.

Let's say you want to keep a log of the changes in the `counter` signal.

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

export const counter = $signal(0);

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

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

## Communicating with Apex data and other asynchronous operations

You can also use the signals framework to communicate with Apex data and other asynchronous operations.
Expand Down Expand Up @@ -817,7 +941,8 @@ The following storage helpers are available by default:
if using a platform event, this will contain the fields of the platform event.

- The `options` (optional) parameter is an object that can contain the following properties (all of them optional):
- `replayId` The replay ID to start from, defaults to -1. When -2 is passed, it will replay from the last saved event.
- `replayId` The replay ID to start from, defaults to -1. When -2 is passed, it will replay from the last saved
event.
- `onSubscribe` A callback function called when the subscription is successful.
- `onError` A callback function called when an error response is received from the server for
handshake, connect, subscribe, and unsubscribe meta channels.
Expand Down
1 change: 1 addition & 0 deletions examples/demo-signals/lwc/demoSignals/demoSignals.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./contact-info";
export * from "./apex-fetcher";
export * from "./shopping-cart";
export * from "./chat-data-source";
export * from "./error-handler";
31 changes: 31 additions & 0 deletions examples/demo-signals/lwc/demoSignals/error-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { $signal, $computed, $effect } from "c/signals";

const anySignal = $signal(0);

$computed(
() => {
anySignal.value;
throw new Error("An error occurred during a computation");
},
{
onError: (error /*_previousValue*/) => {
console.error("error thrown from computed", error);
// Allows for a fallback value to be returned when an error occurs.
return 0;
cesarParra marked this conversation as resolved.
Show resolved Hide resolved

// The previous value can also be returned to keep the last known value.
// return previousValue;
}
}
);

$effect(
() => {
throw new Error("An error occurred during an effect");
},
{
onError: (error) => {
console.error("error thrown from effect", error);
}
}
);
Loading
Loading