Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Last active November 15, 2024 18:08
Show Gist options
  • Save samselikoff/4ae1cd3f299208b6d87cba9fcbb5b673 to your computer and use it in GitHub Desktop.
Save samselikoff/4ae1cd3f299208b6d87cba9fcbb5b673 to your computer and use it in GitHub Desktop.

Version 1

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.

Version 2

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>
  )
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment