import { useActionState } from "react";
async function addToCart(itemId) {
'use server'
if (db.add(itemId)) {
return "success"
} else {
return "error"
}
}
async function checkout() {
'use server'
db.checkout()
}
function Form({}) {
return (
<div>
<button formAction={() => addToCart(1)}>Add item 1</button>
<button formAction={() => addToCart(2)}>Add item 2</button>
<button formAction={() => checkout()}>Checkout</button>
</div>
)
}
- add 1 succeeds
- add 2 fails
- Checkout
In client react, you would always know that item 2 failed by the time you click checkout, because it's synchronous/immediate.
But with async you don't.
Why don't we want to disable buttons?
Most of the time item 1 and 2 are in stock. If we disable, we lose impulse shoppers. CEO says don't disables buttons. They want to tap, tap, fire, and most of †he time items are in stock.
So how to fix?
useActionState.
Refactor.
import { useActionState } from "react";
async function addToCart(itemId) {
'use server'
try {
let item = db.add(itemId);
return { ok: true, itemId }
} catch (error) {
return { ok: false, itemId }
}
}
async function checkout() {
'use server'
db.checkout()
}
function Form({}) {
let [state, dispatch] = useActionState(async (prev, action) => {
switch (action.type) {
case 'add-item':
let { itemId, ok } = await addToCart(action.id)
if (ok) {
return { ...prev, itemsInCart: [...prev.itemsInCart, itemId] }
} else {
return { ...prev, itemsFailed: [...prev.itemsFailed, itemId], error: true }
}
case 'checkout':
if (!action.error) {
await checkout()
return {...prev, checkout: true}
} else {
return prev
}
default:
break;
}
return prev
}, { itemsInCart: [], itemsFailed: [], error: false, checkout: false })
return (
<div>
<button formAction={() => dispatch({ type: 'add-item', id: 1 })}>Add item 1</button>
<button formAction={() => dispatch({ type: 'add-item', id: 2 })}>Add item 2</button>
<p>Current cart:</p>
<div>{state.itemsInCart.join(', ')}</div>
{state.error && (
<p>Failed:</p>
<div>{state.itemsFailed.join(', ')}</div>
)}
<button formAction={() => dispatch({ type: 'checkout' })}>Checkout</button>
</div>
)
}