How-to: Debounce inside `startTransition`
Unanswered
Thrianta posted this in #help-forum
ThriantaOP
Im making use of optimistic updates for a quantity counter, and don't want to fire off API calls when the user spams the up or down arrows. To get the optimistic updates to work, I had to wrap my update logic with
startTransition. The issue now is that when I wrap my updateCount function (this ultimately makes the API call) with a debouncer, the optimistic updates fail to work properly: the optimistic value flashes before returning to the non-optimistic value.type Props = {
available: number
user: number
updateCount: (newQuant: number) => Promise<void>
}
export const ItemCounter: React.FC<Props> = ({
available,
user,
updateCount
}) => {
const [userQuantity, setUserQuantity] = useOptimistic(user)
const handleUpdate = async (newUserQuantity: number) =>
startTransition(async () => {
if (newUserQuantity < 0) {
return
}
if (newUserQuantity > available) {
return
}
setUserQuantity(newUserQuantity)
await updateCount(newUserQuantity) // want to wrap this with a debouncer
})
return (...)
}6 Replies
@Thrianta Im making use of optimistic updates for a quantity counter, and don't want to fire off API calls when the user spams the up or down arrows. To get the optimistic updates to work, I had to wrap my update logic with `startTransition`. The issue now is that when I wrap my `updateCount` function (this ultimately makes the API call) with a debouncer, the optimistic updates fail to work properly: the optimistic value flashes before returning to the non-optimistic value.
tsx
type Props = {
available: number
user: number
updateCount: (newQuant: number) => Promise<void>
}
export const ItemCounter: React.FC<Props> = ({
available,
user,
updateCount
}) => {
const [userQuantity, setUserQuantity] = useOptimistic(user)
const handleUpdate = async (newUserQuantity: number) =>
startTransition(async () => {
if (newUserQuantity < 0) {
return
}
if (newUserQuantity > available) {
return
}
setUserQuantity(newUserQuantity)
await updateCount(newUserQuantity) // want to wrap this with a debouncer
})
return (...)
}
First you need to remove the async part from your startTransition function. It's made for:
startTransition lets you update the state without blocking the UI.And as the react docs say:
The function you pass to startTransition must be synchronousSo please remove it from there. If you want to make client side mutations that require a server, use server actions. Either use them directly or use a wrapper for them to make your work easier. I like to use https://next-safe-action.dev/
ThriantaOP
Ok sure I removed the async and await keywords from the
Before posting this question I saw your reponse to another user where you recommended next-safe-actions and useEffect to implement debouncing. But I am unsure if this will work for me.
startTransition callback. I am using a server action to make client side mutations. My ItemList component passes down a callback (updateCount) to the Item components. This callback handles calling the server action and updating a zustand store with the response.Before posting this question I saw your reponse to another user where you recommended next-safe-actions and useEffect to implement debouncing. But I am unsure if this will work for me.
@Thrianta Ok sure I removed the async and await keywords from the `startTransition` callback. I am using a server action to make client side mutations. My `ItemList` component passes down a callback (`updateCount`) to the `Item` components. This callback handles calling the server action and updating a zustand store with the response.
Before posting this question I saw your reponse to another user where you recommended next-safe-actions and useEffect to implement debouncing. But I am unsure if this will work for me.
yea, I don't know, what you want to debounce, when updating a number... the number state itself can be handled clientside and updated in the background. So if the user clicks a button for "updateQuantity" you can add 1 to your clientside state (so it will be directly visible) and use a debounce function in the background, that updates the DB.
If the user spam clicks your button now, the clientside state will be directly counted up (everytime) and the quantity for the DB will be updated only once (after the user stopped spam clicking the button).
If the user spam clicks your button now, the clientside state will be directly counted up (everytime) and the quantity for the DB will be updated only once (after the user stopped spam clicking the button).
ThriantaOP
I need help with separating the client side number and the server call. At the moment i have a button onclick handler that controls updating the clientside value and making the server call. This all happens inside the startTransition for the optimistic value.
@Thrianta I need help with separating the client side number and the server call. At the moment i have a button onclick handler that controls updating the clientside value and making the server call. This all happens inside the startTransition for the optimistic value.
wanna go step by step though the process?
(if not https://nextjs-forum.com/post/1267435011943039019#message-1267454205283795028 use this)
(if not https://nextjs-forum.com/post/1267435011943039019#message-1267454205283795028 use this)
ThriantaOP
yes please. if not in a few hours from now, maybe tomorrow. i have work to do on another project.
i think its mainly the
i can successfully debounce fn calls outside of the
i think its mainly the
startTransition fn throwing me off. i havent used it before. and in the [react docs](https://react.dev/reference/react/useOptimistic) for useOptimistic they dont use it but it is sort of explained [here](https://youtu.be/Pz8CAbeg6Q0?t=1675). i can successfully debounce fn calls outside of the
startTransition, but once i put the debounced caller inside it something goes wrong and i get confused