Skip to content

Commit

Permalink
examples: confrom init (#279)
Browse files Browse the repository at this point in the history
* examples: confrom init

* example: conform example: use src directory, add next.config.js

* example: conform example's remove button not working

---------

Co-authored-by: akfm.sato <01047245@CF0286.local>
  • Loading branch information
2 people authored and akfm.sato committed Mar 28, 2024
1 parent c1bd17b commit 2aebb40
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 13 deletions.
36 changes: 36 additions & 0 deletions apps/example-next-conform/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
12 changes: 12 additions & 0 deletions apps/example-next-conform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Examples next app with conform

## Getting Started

```
$ pnpm i
$ pnpm run dev
```

## Conform

https://conform.guide/integration/nextjs
15 changes: 15 additions & 0 deletions apps/example-next-conform/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
scrollRestoration: true,
},
productionBrowserSourceMaps: true,
webpack: (config, { isServer }) => {
if (isServer) {
config.devtool = "source-map";
}
return config;
},
};

module.exports = nextConfig;
22 changes: 22 additions & 0 deletions apps/example-next-conform/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"private": true,
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@conform-to/react": "1.0.6",
"@conform-to/zod": "1.0.6",
"next": "14.1.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"zod": "3.22.4"
},
"devDependencies": {
"@types/node": "20.11.30",
"@types/react": "18.2.67",
"@types/react-dom": "18.2.22",
"typescript": "5.4.3"
}
}
19 changes: 19 additions & 0 deletions apps/example-next-conform/src/app/dynamic-form/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use server";

import { parseWithZod } from "@conform-to/zod";
import { redirect } from "next/navigation";
import { teamSchema } from "./schema";

export async function saveTeam(prevState: unknown, formData: FormData) {
const submission = parseWithZod(formData, {
schema: teamSchema,
});

if (submission.status !== "success") {
return submission.reply();
}

console.log("submit data", submission.value);

redirect("/success");
}
71 changes: 71 additions & 0 deletions apps/example-next-conform/src/app/dynamic-form/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";

import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { useFormState } from "react-dom";
import { saveTeam } from "./action";
import { teamSchema } from "./schema";

export default function Form() {
const [lastResult, action] = useFormState(saveTeam, undefined);
const [form, fields] = useForm({
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, { schema: teamSchema });
},
shouldValidate: "onBlur",
});
const members = fields.members.getFieldList();

return (
<form {...getFormProps(form)} action={action} noValidate>
<button
type="submit"
{...form.insert.getButtonProps({
name: fields.members.name,
})}
>
Add member
</button>
{members.map((member, index) => {
const memberFields = member.getFieldset();

return (
<div key={member.key}>
<div>Member {index + 1}</div>
<input type="hidden" name={memberFields.id.name} value={index} />
<label htmlFor={memberFields.name.id}>
name:&nbsp;
<input name={memberFields.name.name} />
</label>
<label>
leader:&nbsp;
<input
{...getInputProps(fields.leaderId, {
type: "radio",
value: index.toString(),
})}
/>
</label>
<label>
engineer:&nbsp;
<input name={memberFields.engineer.name} type="checkbox" />
</label>
<button
type="submit"
{...form.remove.getButtonProps({
name: fields.members.name,
index,
})}
>
Remove
</button>
<div>{memberFields.name.errors?.join(", ")}</div>
<div>{memberFields.engineer.errors?.join(", ")}</div>
</div>
);
})}
<button type="submit">submit</button>
</form>
);
}
10 changes: 10 additions & 0 deletions apps/example-next-conform/src/app/dynamic-form/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Form from "./form";

export default function Page() {
return (
<main>
<h1>Dynamic Form</h1>
<Form />
</main>
);
}
19 changes: 19 additions & 0 deletions apps/example-next-conform/src/app/dynamic-form/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

export const teamSchema = z.object({
leaderId: z.string({
required_error: "Leader is required",
}),
members: z.array(
z.object({
id: z.string().min(1),
name: z
.string({
required_error: "Name is required",
})
.min(1)
.max(100),
engineer: z.preprocess((x) => x === "on", z.boolean()),
}),
),
});
11 changes: 11 additions & 0 deletions apps/example-next-conform/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
22 changes: 22 additions & 0 deletions apps/example-next-conform/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from "next";
import Link from "next/link";

export const metadata: Metadata = {
title: "`conform` example",
};

export default function Home() {
return (
<main>
<h1>`conform` example</h1>
<ul>
<li>
<Link href="/simple-form">simple-form</Link>
</li>
<li>
<Link href="/dynamic-form">dynamic-form</Link>
</li>
</ul>
</main>
);
}
19 changes: 19 additions & 0 deletions apps/example-next-conform/src/app/simple-form/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use server";

import { parseWithZod } from "@conform-to/zod";
import { redirect } from "next/navigation";
import { userSchema } from "./schema";

export async function saveUser(prevState: unknown, formData: FormData) {
const submission = parseWithZod(formData, {
schema: userSchema,
});

if (submission.status !== "success") {
return submission.reply();
}

console.log("submit data", submission.value);

redirect("/success");
}
42 changes: 42 additions & 0 deletions apps/example-next-conform/src/app/simple-form/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { useFormState } from "react-dom";
import { saveUser } from "./action";
import { userSchema } from "./schema";

export default function Form() {
const [lastResult, action] = useFormState(saveUser, undefined);
const [form, fields] = useForm({
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, { schema: userSchema });
},
shouldValidate: "onBlur",
});

return (
<form {...getFormProps(form)} action={action} noValidate>
<div>
<label htmlFor={fields.firstName.id}>First name</label>
<input
{...getInputProps(fields.firstName, {
type: "text",
})}
/>
<div>{fields.firstName.errors}</div>
</div>
<div>
<label htmlFor={fields.lastName.id}>Last name</label>
<input
{...getInputProps(fields.lastName, {
type: "text",
})}
/>
<div>{fields.lastName.errors}</div>
</div>
<button type="submit">Submit</button>
</form>
);
}
10 changes: 10 additions & 0 deletions apps/example-next-conform/src/app/simple-form/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Form from "./form";

export default function Page() {
return (
<main>
<h1>Simple Form</h1>
<Form />
</main>
);
}
16 changes: 16 additions & 0 deletions apps/example-next-conform/src/app/simple-form/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from "zod";

export const userSchema = z.object({
firstName: z
.string({
required_error: "`First name` is required",
})
.min(1)
.max(100),
lastName: z
.string({
required_error: "`Last name` is required",
})
.min(1)
.max(100),
});
12 changes: 12 additions & 0 deletions apps/example-next-conform/src/app/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Link from "next/link";

export default function Page() {
return (
<main>
<h1>Success!</h1>
<p>
<Link href="/">top page</Link>
</p>
</main>
);
}
25 changes: 25 additions & 0 deletions apps/example-next-conform/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Loading

0 comments on commit 2aebb40

Please sign in to comment.