-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Add getter api to @xstate/store in main #5191
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 02fa5ce The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@davidkpiano I managed to resolve all the issue with the PR. Please review and let me know what you think. |
36cccf1
to
02fa5ce
Compare
Also, sorry for the delay, but can you explain the benefits of getters, over more explicit selectors? |
By incorporating Imagine our cart store has the following raw state:
import { createStore } from '@xstate/store';
const cartStore = createStore({
context: {
items: [
{ id: 1, name: 'Widget', price: 50, quantity: 2, discount: 0.10, taxRate: 0.08 },
{ id: 2, name: 'Gadget', price: 30, quantity: 1, discount: 0.05, taxRate: 0.08 }
],
shippingThreshold: 100,
baseShippingCost: 10
},
getters: {
subtotal: (ctx) =>
ctx.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
// Total discount applied
totalDiscount: (ctx) =>
ctx.items.reduce(
(sum, item) => sum + item.price * item.quantity * item.discount,
0
),
// Tax calculated on the net amount (subtotal minus discount)
taxAmount: (ctx, getters) =>
(getters.subtotal - getters.totalDiscount) *
(ctx.items.length ? ctx.items[0].taxRate : 0),
// Shipping cost is waived if subtotal exceeds threshold
shippingCost: (ctx, getters) =>
getters.subtotal >= ctx.shippingThreshold ? 0 : ctx.baseShippingCost,
// Total amount to be paid
total: (ctx, getters) =>
getters.subtotal - getters.totalDiscount + getters.taxAmount + getters.shippingCost,
// Formatted string for display
formattedTotal: (ctx, getters) =>
`$${getters.total.toFixed(2)}`
},
on: {
addItem: (ctx, event) => ({
...ctx,
items: [...ctx.items, event.item]
}),
updateItem: (ctx, event) => ({
...ctx,
items: ctx.items.map(item =>
item.id === event.item.id ? { ...item, ...event.item } : item
)
}),
removeItem: (ctx, event) => ({
...ctx,
items: ctx.items.filter(item => item.id !== event.itemId)
})
}
}); Without getters, any component that needs to display the subtotal, discounts, taxes, shipping, or total must recalculate them from the raw state. That leads to duplication and makes maintenance a nightmare if the business rules change.
|
In short, the PR’s core value is that it shifts the burden of derived state computation into the store, providing a “batteries‑included” solution. |
And I just want to add I would still use selectors for view specific logic, this is not a replacement. For domain logic I would use getters, for view specific rendering, or cross store derivations, I would use selectors. Imagine if you wanted to use the "ShoppingCart" entity on the backend (where there are no views) to validate incoming fields on a request is computed using the same business logic. I would want the logic to exist on the entity, not on the view. |
#5184
Added store getters API for computed values derived from context: