I have a store with a list of entities, and another Store with and object that include one of those entities.
I want changes in the first store to be reactively reflected on the second.
I'll provide a quick example with a list of items and a list of invoices
export type Invoice = {
id: string
customer: string
items: InvoiceItem[]
}
export type InvoiceItem = {
id: string
name: string
price: number
}
Whenever the name or price of an invoice item is updated I'd like all the related Invoices to also be updated.
I created this very simple example (repl available here) but in order for the $invoices store to be updated I have to issue a $invoices = $invoices whenever the $items store changes.
Another more elegant way to do it is to subscribe to the items store and from there update the invoices store, like this:
items.subscribe(_ => invoices.update(data => data))
<script>
import { writable } from 'svelte/store'
let item1 = { id: 'item-01', name: 'Item number 01', price: 100 }
let item2 = { id: 'item-02', name: 'Item number 02', price: 200 }
let item3 = { id: 'item-03', name: 'Item number 03', price: 300 }
let items = writable([item1, item2, item3])
let invoices = writable([
{ id: 'invoice-0', customer: 'customer1', items: [item1, item3] }
])
items.subscribe(_ => invoices.update(data => data)) // refresh invoices store whenever an item is changed
const updateItem1 = () => {
$items[0].price = $items[0].price + 10
// $invoices = $invoices // alternatively, manually tell invoices store that something changed every time I change and item!!!
}
</script>
<button on:click={updateItem1}>update item 1 price</button>
<hr />
<textarea rows="18">{JSON.stringify($invoices, null, 2)}</textarea>
<textarea rows="18">{JSON.stringify($items, null, 2)}</textarea>
Is this the best way to handle this kind of scenario?
Update: thanks to the great answers and comments I came out with this more complete example: see this repl
I added some functionality that I hope will serve as basis for similar common scenarios
This is how my store api ended up:
// items.js
items.subscribe // read only store
items.reset()
items.upsert(item) // updates the specified item, creates a new one if it doesn't exist
// invoices.js
invoices.subscribe // read only store
invoices.add(invocieId, customer, date) // adds a new invoice
invoices.addLine(invoiceId, itemId, quantity)
invoices.getInvoice(invoice) // get a derived store for that particular invoice
invoice.subscribe // read only store
invoice.addLine(itemId, quantity)
A few highlights
lines
array, each with an item and a quantityitems.subscribe(() => set(_invoices))
The solution depends on whether or not you need items
independently (one item can be part of multiple invoices) or if it can be part of the invoices. If they can be one big blob, I would create invoices
as a store and provide methods to update specific invoices. The items
store then would be derived from the invoices
.
// invoices.ts
const _invoices = writable([]);
// public API of your invoices store
export const invoices = {
subscribe: _invoices.subscribe,
addItemToInvoice: (invoideId, item) => {...},
..
};
// derived items:
const items = derived(invoices, $invoices => flattenAllInvoiceItems($invoice));
However, if they need to be separate - or if it is easier to handle item updates that way -, then I would only store the IDs of the items in the invoice store and create a derived store which uses invoices+items to create the full invoices.
// items.ts
const _items = writable([]);
// public API of your items store
export const items = {
subscribe: _items.subscribe,
update: (item) => {...},
...
};
// invoices.ts
import { items } from './items';
const _invoices = writable([]);
// public API of your invoices store
export const invoices = {
// Assuming you never want the underlying _invoices state avialable publicly
subscribe: derived([_invoices, items], ([$invoices, $items]) => mergeItemsIntoInvoices($invoices, $items)),
addItemToInvoice: (invoideId, item) => {...},
..
};
In both cases you can use invoices
and items
in your Svelte components like you want, interact with a nice public API and the derived stores will ensure everything is synched.