-
How can be implemented an atom to hold either a value or a calculated value at the same time? For instance imaging the case of an order line when you have:
From the state: state = {
unitPrice: 100,
discount: 0,
price: 100
} when I change the discount to 20, price gets calculated: state = {
unitPrice: 100,
discount: 20,
price: 80
} the same state is reached if I change price to 80. The formula for I implemented it using unitPriceAtom, discountAtom, priceAtom, discountFormulaAtom, priceFormulaAtom and atomEffect. But it results in an infinite loop. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 10 replies
-
function getNextPrice(unitPrice: number, discount: number) {
return unitPrice * (1 - discount / 100)
}
function getNextDiscount(unitPrice: number, price: number) {
return ((unitPrice - price) / unitPrice) * 100
}
// reacts to changes to unitPrice
const unitPriceAtom = withAtomEffect(atom(100), (get, set) => {
const unitPrice = get(unitPriceAtom)
set(priceAtom, getNextPrice(unitPrice, get.peek(discountAtom)))
set(discountAtom, getNextDiscount(unitPrice, get.peek(priceAtom)))
})
// reacts to changes to discount
const discountAtom = withAtomEffect(atom(0), (get, set) => {
const discount = get(discountAtom)
if (discount === 20 || discount === 80) {
set(priceAtom, getNextPrice(get.peek(unitPriceAtom), discount))
}
})
// reacts to changes to price
const priceAtom = withAtomEffect(atom(100), (get, set) => {
set(discountAtom, getNextDiscount(get.peek(unitPriceAtom), get(priceAtom)))
})
const priceAndDiscount = atom((get) => ({
unitPrice: get(unitPriceAtom),
discount: get(discountAtom),
price: get(priceAtom),
}))
const store = createStore()
store.sub(priceAndDiscount, () => void 0)
await delay(0)
expect(store.get(priceAtom)).toBe(100) // value
expect(store.get(discountAtom)).toBe(0) // (100-100)/100*100 = 0)
store.set(discountAtom, 20)
await delay(0)
expect(store.get(priceAtom)).toBe(80) // 100*(1-20/100) = 80)
expect(store.get(discountAtom)).toBe(20) // value
store.set(priceAtom, 50)
await delay(0)
expect(store.get(priceAtom)).toBe(50) // value
expect(store.get(discountAtom)).toBe(50) // (100-50)/100*100 = 50)
store.set(unitPriceAtom, 200)
await delay(0)
expect(store.get(priceAtom)).toBe(100) // 200*(1-50/100) = 100)
expect(store.get(discountAtom)).toBe(50) // (200-100)/200*100 = 50) |
Beta Was this translation helpful? Give feedback.
-
Thanks for the help. I ended up with something like the code below. 3 regular atoms, 2 derived atoms for formulas, 2 effect that copy formula to regular atom. However:
BTW I do not really need this example, what I am doing is just comparing state management libraries. You can close the discussion if you are not interested in adding further details for future reference. const semaphorAtom = atom(() => ({ value: false }))
const unitPriceAtom = atom(100)
const discountAtom = atom(0)
const priceAtom = atom(100)
// Formula to calculate discount.
const discountFormulaAtom = atom((get) => {
const unitPrice = get(unitPriceAtom)
const price = get(priceAtom)
const discount = unitPrice == 0 ? 0 : (unitPrice - price) / unitPrice * 100;
return discount
})
// Formula to calculate price.
const priceFormulaAtom = atom((get) => {
const unitPrice = get(unitPriceAtom)
const discount = get(discountAtom)
const price = unitPrice * (1 - discount / 100)
return price
})
// Copy formula for discount to discount.
const subDisocuntEffect = atomEffect((get, set) => {
const sem = get(semaphorAtom)
console.log('discount sem', sem.value)
if (sem.value) return
sem.value = true
let value = get(discountFormulaAtom)
console.log('discount', value)
if (value == undefined)
value = 0
//value = value + 0.01 // <-- generate infinite loop when added. (a)
set(discountAtom, value)
sem.value = false
})
// Copy formula for price to price.
const subPriceEffect = atomEffect((get, set) => {
const sem = get(semaphorAtom)
console.log('price sem', sem.value)
if (sem.value) return
sem.value = true
let value = get(priceFormulaAtom)
console.log('price', value)
if (value == undefined)
value = 0
set(priceAtom, value)
sem.value = false
})
function App() {
const [price, setPrice] = useAtom(unitPriceAtom)
const [discount, setDiscount] = useAtom(discountAtom)
const [total, setTotal] = useAtom(priceAtom)
useAtom(subDisocuntEffect)
useAtom(subPriceEffect)
... |
Beta Was this translation helpful? Give feedback.
I should also mention that the vanilla 'jotai' way to do this is via a push based mechanic rather than a pull based one.