Skip to content

Commit

Permalink
Merge pull request #30 from IdoPesok/use-form-state-docs
Browse files Browse the repository at this point in the history
added docs for useFormState hook to address #29
  • Loading branch information
IdoPesok authored May 24, 2024
2 parents 21f74a6 + 2e07836 commit 44fbee1
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 3 deletions.
3 changes: 3 additions & 0 deletions examples/showcase/components/markdown/example-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import IncrementExample from "@/content/examples/introduction/increment-example"
import HelloWorld from "@/content/examples/react-query/hello-world-action"
import RandomNumberExampleDisplay from "@/content/examples/refetching-queries/random-number-example-display"
import RandomNumberExampleRefetch from "@/content/examples/refetching-queries/random-number-example-refetch"
import UseFormStateExample from "@/content/examples/use-form-state/use-form-state-example"
import { memo } from "react"

function ExampleComponent({ id }: { id: string }) {
Expand All @@ -17,6 +18,8 @@ function ExampleComponent({ id }: { id: string }) {
return <IncrementExample />
case "form-data":
return <FormDataExample />
case "use-form-state":
return <UseFormStateExample />
default:
return <div className="p-4 border rounded">{id}</div>
}
Expand Down
18 changes: 18 additions & 0 deletions examples/showcase/content/examples/use-form-state/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use server"

import z from "zod"
import { createServerAction } from "zsa"

export const produceNewMessage = createServerAction()
.input(
z.object({
name: z.string(),
}),
{
type: "formData",
}
)
.handler(async ({ input }) => {
await new Promise((resolve) => setTimeout(resolve, 500))
return "Hello, " + input.name
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { useFormState } from "react-dom"
import { toast } from "sonner"
import { produceNewMessage } from "./actions"

export default function UseFormStateExample() {
let [messages, submitAction] = useFormState(
async (previousState: string[], formData: FormData) => {
const [data, err] = await produceNewMessage(formData)

if (err) {
toast.error("Error!!!")
return previousState
}

return [...previousState, data]
},
["my initial message"]
)

return (
<Card className="not-prose">
<CardHeader>
<CardTitle>Use Form State</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<form action={submitAction} className="flex flex-col gap-4">
<Input name="name" placeholder="Enter your name..." />
<Button>Create message</Button>
</form>
<h1>Messages:</h1>
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
</div>
</CardContent>
</Card>
)
}
93 changes: 93 additions & 0 deletions examples/showcase/content/use-form-state.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: "useFormState"
group: "Client Side Querying"
groupOrder: -10
---

# useFormState

<Warning>
useFormState has been **renamed to useActionState** in newer versions of
React. The gist of these docs is the same, but the API has changed slightly.
</Warning>

### Server Code

When defining your action on the server, be sure to set the `type` to `"formData"` to indicate that the input is a `FormData` object.

```typescript:actions.ts
"use server" <|highlight|>

import z from "zod"
import { createServerAction } from "zsa"

export const produceNewMessage = createServerAction()
.input(
z.object({
name: z.string(),
}),
{
type: "formData", <|highlight|>
}
)
.handler(async ({ input }) => {
await new Promise((resolve) => setTimeout(resolve, 500))
return "Hello, " + input.name
})
```

### Client Code

On the client, you can use `useFormState` to manage the state of your form. This hook takes two arguments: a function that will be called with the previous state and the form data, and an array of initial values.
In return, you will get a variable holding the current state and a function to submit the form.

```typescript:my-client-component.tsx
"use client" <|highlight|>

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { useFormState } from "react-dom" <|highlight|>
import { toast } from "sonner"
import { produceNewMessage } from "./actions"

export default function UseFormStateExample() {
let [messages, submitAction] = useFormState( <|highlight|>
async (previousState: string[], formData: FormData) => { <|highlight|>
const [data, err] = await produceNewMessage(formData) <|highlight|>
<|highlight|>
if (err) { <|highlight|>
toast.error("Error!!!") <|highlight|>
return previousState <|highlight|>
} <|highlight|>
<|highlight|>
return [...previousState, data] <|highlight|>
}, <|highlight|>
["my initial message"] <|highlight|>
) <|highlight|>

return (
<Card className="not-prose">
<CardHeader>
<CardTitle>Use Action State</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<form action={submitAction} className="flex flex-col gap-4"> <|highlight|>
<Input name="name" placeholder="Enter your name..." /> <|highlight|>
<Button>Create message</Button> <|highlight|>
</form> <|highlight|>
<h1>Messages:</h1>
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
</div>
</CardContent>
</Card>
)
}
```

### Result

<ExampleComponent id="use-form-state" />
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 44fbee1

Please sign in to comment.