Skip to content

Commit

Permalink
temp
Browse files Browse the repository at this point in the history
  • Loading branch information
cesarParra committed Jun 1, 2024
1 parent a10f96f commit 315e27b
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 26 deletions.
99 changes: 93 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A simple yet powerful reactive state management solution for Lightning Web Compo

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 signalss
reactive signals for Lightning Web Components that allows you to create reactive data signals
that can be used to share state between components.

It features:
Expand Down Expand Up @@ -203,7 +203,7 @@ export default class ContactInfoForm extends LightningElement {
}
```

**You can create a computed value that depends on both signalss**
**You can create a computed value that depends on both signals**

```html
<!-- businessCard.html -->
Expand All @@ -221,7 +221,7 @@ export default class ContactInfoForm extends LightningElement {
// businessCard.js
import { LightningElement } from "lwc";
import { $computed } from "c/signals";
import { accountName, contactName } from "c/demoSignalss";
import { accountName, contactName } from "c/demoSignals";

export default class BusinessCard extends LightningElement {
contactInfo = $computed(
Expand Down Expand Up @@ -421,7 +421,7 @@ Let's now create our picklist component that allows the user to select an accoun
// accountPicker.js
import { LightningElement, track, wire } from "lwc";
import getAccounts from "@salesforce/apex/ResourceController.getAccounts";
import { selectedAccountId } from "c/demoSignalss";
import { selectedAccountId } from "c/demoSignals";

export default class AccountPicker extends LightningElement {
@track accounts = [];
Expand Down Expand Up @@ -486,7 +486,7 @@ Now, let's create the component that displays the details of the selected accoun
// accountDetails.js
import { LightningElement } from "lwc";
import { $computed } from "c/signals";
import { getAccount } from "c/demoSignalss";
import { getAccount } from "c/demoSignals";

export default class AccountDetails extends LightningElement {
account = $computed(() => (this.account = getAccount.value)).value;
Expand Down Expand Up @@ -550,6 +550,93 @@ export default class ContactList extends LightningElement {
}
```

### Mutating `$resource` data

Besides `refetch`, the `$resource` function also returns a `mutate` function that allows you to mutate the data.

`mutate` is useful when you want to update the data without refetching it (and avoid a trip to the server).

It receives a single value, which will be set as the new value of the data. The `resource` value will be updated
immediately, the `.loading` property will be set to `false`, and the `.error` property will be set to `null`.

```javascript
import { $resource } from "c/signals";

const { data, mutate } = $resource(asyncFunction);

mutate("new value");
```

#### Reacting to mutated values

When using the `mutate` function, you might want to react to the changes in the data. For example, you might now
want to call an Apex function to save the new value to the server, and make sure the data is synced.

For this, you can provide a function through the options object's `onMutate`.

The function you provide can receive 3 arguments:

- The new value
- The old value
- A `mutate` function that you can use the update the data again. This can be used for when you want to update the data
based on what was returned from the server, but you don't want to refetch the data. You SHOULD use this mutate
function over the one returned when creating the `$resource` because this will not trigger `onMutate` once again.

```javascript
import { $resource } from "c/signals";
import getContacts from "@salesforce/apex/ContactController.getContacts";
import saveContacts from "@salesforce/apex/ContactController.saveContacts";

const { data, mutate } = $resource(
getContacts,
{},
{
onMutate: async (newValue, oldValue, mutate) => {
await saveContacts({ contacts: newValue });
mutate(newValue);
}
}
);
```

In the case an error occurs on your server call, the `mutate` you can pass an error object as the second argument to
the `mutate` function. This will set the `.error` property of the `resource` to the error object.

```javascript
import { $resource } from "c/signals";

const { data, mutate } = $resource(asyncFunction);

try {
await saveContacts({ contacts: newValue });
mutate(newValue);
} catch (error) {
mutate(null, error);
}
```

#### Optimistic updating

When you mutate a `resource` as exemplified above, you can achieve the concept of optimistic updating. This is when you
update the value immediately before the server responds, and then update the value again when the server responds.

Optimistically updating the value can provide a better user experience by making the UI feel more responsive, but it
can also lead to inconsistencies if the server responds with an error. So if you wish to turn this off, and
manage updating the value yourself, either by `refetching` or by using an `onMutate` function, you can set the
`optimisticMutate` option to `false`.

```javascript
import { $resource } from "c/signals";

const { data, refetch, mutate } = $resource(
asyncFunction,
{},
{
optimisticMutate: false
}
);
```

## Storage

By default, any created signal is stored in memory and will be lost when the component is destroyed. This behavior
Expand All @@ -568,7 +655,7 @@ The following storage helpers are available by default:
- `useCookies(key: string, expires?: Date)`: Stores the signal in a cookie with the given key. You can also pass an
optional `expires` parameter to set the expiration date of the cookie

### Creating custom storage
### Creating a custom storage

The `storage` option receives a function that defines the behavior for where the data should be stored.
This means you can create your own custom storage solution by passing a function with the following
Expand Down
24 changes: 13 additions & 11 deletions examples/demo-signals/lwc/demoSignals/shopping-cart.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $resource } from "c/signals";
import { $signal, $resource } from "c/signals";
import getShoppingCart from "@salesforce/apex/ShoppingCartController.getShoppingCart";
import updateShoppingCart from "@salesforce/apex/ShoppingCartController.updateShoppingCart";

Expand All @@ -19,17 +19,19 @@ import updateShoppingCart from "@salesforce/apex/ShoppingCartController.updateSh
*/

// Store each state change in the cart history
const cartHistory = [];
export const cartHistory = $signal([]);
let isUndoing = false;

export function undoCartChange() {
export const undoCartChange = () => {
isUndoing = true;
const lastState = cartHistory.pop();
const lastState = cartHistory.value[cartHistory.value.length - 1];
// Remove the last state from the history
cartHistory.value = cartHistory.value.slice(0, -1);
if (lastState) {
updateCart(lastState);
}
isUndoing = false;
}
};

/**
* Updates the cart on the server
Expand All @@ -39,6 +41,10 @@ export function undoCartChange() {
*/
async function updateCartOnTheServer(newCart, previousValue, mutate) {
try {
// Keep track of the isUndoing value before making any async changes
// to ensure we don't update the history when undoing, even after
// an async operation.
const shouldUpdateHistory = !isUndoing;
// Update the cart on the server
const updatedShoppingCart = await updateShoppingCart({
newItems: newCart.items
Expand All @@ -48,18 +54,14 @@ async function updateCartOnTheServer(newCart, previousValue, mutate) {
mutate(updatedShoppingCart);

// Store the previous value in the history
if (!isUndoing) {
cartHistory.push(previousValue);
if (shouldUpdateHistory) {
cartHistory.value = [...cartHistory.value, previousValue];
}
} catch (error) {
mutate(null, error);
}
}

// TODO: When we document this, remember there are 2 options. We can either
// do the update the same way we are doing it here (through onMutate) or
// it can be done in the component (or anywhere really) and then `refetch` can be called.

export const { data: shoppingCart, mutate: updateCart } = $resource(
getShoppingCart,
{},
Expand Down
2 changes: 1 addition & 1 deletion examples/main/default/staticresources/tw/css/main.css

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import TwElement from "c/twElement";
import { $computed } from "c/signals";
import { shoppingCart, updateCart, undoCartChange } from "c/demoSignals";
import {
shoppingCart,
updateCart,
cartHistory,
undoCartChange
} from "c/demoSignals";

// States
import ready from "./states/ready.html";
Expand All @@ -9,6 +14,13 @@ import empty from "./states/empty.html";

export default class ShoppingCartDetails extends TwElement {
itemData = $computed(() => (this.itemData = shoppingCart.value)).value;
cartHistoryLength = $computed(
() => (this.cartHistoryLength = cartHistory.value.length)
).value;

connectedCallback() {
super.connectedCallback();
}

render() {
return this.itemData.loading
Expand Down Expand Up @@ -56,6 +68,10 @@ export default class ShoppingCartDetails extends TwElement {
updateCart({ items: newItems });
}

get displayUndoPanel() {
return this.cartHistoryLength > 0;
}

undo() {
undoCartChange();
}
Expand Down
53 changes: 46 additions & 7 deletions examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ <h1 class="text-3xl font-bold tracking-tight text-gray-900">
</h1>
<div>
<h2 class="sr-only">Items in your shopping cart</h2>
<button
class="mt-4 text-sm font-medium text-indigo-600 hover:text-indigo-500"
type="button"
onclick={undo}
>
Undo last change
</button>
<ul
role="list"
class="divide-y divide-gray-200 border-b border-t border-gray-200"
Expand Down Expand Up @@ -98,4 +91,50 @@ <h3 class="text-sm">
</ul>
</div>
</div>
<div
if:true={displayUndoPanel}
aria-live="assertive"
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6"
>
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<div
class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="p-4">
<div class="flex items-center">
<div class="flex w-0 flex-1 justify-between">
<p class="w-0 flex-1 text-sm font-medium text-gray-900">
Changes have been made to the cart
</p>
<button
onclick={undo}
type="button"
class="ml-3 flex-shrink-0 rounded-md bg-white text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Undo
</button>
</div>
<div class="ml-4 flex flex-shrink-0">
<button
type="button"
class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
<span class="sr-only">Close</span>
<svg
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

0 comments on commit 315e27b

Please sign in to comment.