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.