Incremental data loading with useActionState and yield in Nextjs 15

Table of contents

A few days ago, I had a fucking dream that useActionState can use yield to push data. It was probably that state was an array []. Every time data was yielded in the action function, the data would be appended to the state. (It could also be that I seemed to have seen the code for this implementation somewhere, but I couldn't find it after searching for a whole day.)

Coincidentally, my business scenario today coincided with this implementation method, and I also successfully implemented Incremental data with useActionState and yield in Nextjs 15.

Final effect

Code

// actions.ts
'use server'

export async function* fetchDataAction() {
  yield { index: 1, message: 'Step1', error: null }
  await new Promise((resolve) => setTimeout(resolve, 1000))
  yield { index: 2, message: 'Step2', error: null }
  await new Promise((resolve) => setTimeout(resolve, 1000))
  yield { index: 3, message: 'Step3', error: null }
  await new Promise((resolve) => setTimeout(resolve, 1000))
  yield { index: 4, message: 'Step4', error: null }
  await new Promise((resolve) => setTimeout(resolve, 1000))
  yield { index: 5, message: 'Step5', error: null }
  await new Promise((resolve) => setTimeout(resolve, 1000))
  yield { index: 6, message: 'Step6', error: null }
  await new Promise((resolve) => setTimeout(resolve, 1000))
  yield { index: 7, message: 'Step7', error: null }
}
// page.tsx
'use client'
import { useActionState, useEffect, useState } from 'react'
import { fetchDataAction } from '~/actions'

type PageProps = {}

const Page = ({}: PageProps) => {
  const [currentStep, setCurrentStep] = useState<{
    index: number | null | undefined
    message: string | null | undefined
    error: string | null | undefined
  }>({ index: 0, message: null, error: null })
  const [loading, setLoading] = useState<boolean>(true)
  const [state, formAction] = useActionState(fetchDataAction, undefined)

  useEffect(() => {
    const stepItr = async () => {
      const next = await state?.next()
      const value = next?.value
      const done = next?.done
      if (done) {
        setLoading(false)
        return
      }
      setCurrentStep({ index: value?.index, message: value?.message, error: value?.error })
      console.log(value)
      stepItr()
    }
    if (state) {
      stepItr()
      setLoading(true)
    }
  }, [state])
  return (
    <div>
      <p>
        Current Step Index: {currentStep?.index || 'No Index...'}
      </p>
      <p>
        Current Step Message: {currentStep?.message || 'No Message...'}
      </p>
      <p>
        Current Step Error: {currentStep?.error || 'No Error...'}
      </p>
      <button
        formAction={formAction}
      >
        Start
      </button>
    </div>
  )
}

export default Page

I'm not sure if this will be a problem in production, if there are any please reply to point them out.

Did you find this article valuable?

Support Asiones Jia's blog by becoming a sponsor. Any amount is appreciated!