Skip to content

Commit

Permalink
Merge pull request #150 from seasonedcc/dynamic-form
Browse files Browse the repository at this point in the history
Add dynamic form example
  • Loading branch information
danielweinmann authored Jan 31, 2023
2 parents c9ecc6b + 1470688 commit 9331ea5
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
3 changes: 3 additions & 0 deletions apps/web/app/routes/examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ export default function Component() {
<SidebarLayout.NavLink to={'/examples/forms/imperative-submit'}>
Imperative submit
</SidebarLayout.NavLink>
<SidebarLayout.NavLink to={'/examples/forms/dynamic-form'}>
Dynamic form
</SidebarLayout.NavLink>
<SidebarLayout.NavTitle>renderField</SidebarLayout.NavTitle>
<SidebarLayout.NavLink
to={'/examples/render-field/required-indicator'}
Expand Down
123 changes: 123 additions & 0 deletions apps/web/app/routes/examples/forms/dynamic-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import hljs from 'highlight.js/lib/common'
import type { ActionFunction, LoaderArgs, MetaFunction } from '@remix-run/node'
import { formAction } from '~/formAction'
import { z } from 'zod'
import Form from '~/ui/form'
import { metaTags } from '~/helpers'
import { makeDomainFunction } from 'domain-functions'
import Example from '~/ui/example'
import { useLoaderData } from '@remix-run/react'

const title = 'Dynamic form'
const description =
'In this example, we render a dynamic form with the fields coming from the backend.'

export const meta: MetaFunction = () => metaTags({ title, description })

const code = `type FieldType = 'string' | 'email' | 'int'
type Field = { name: string; type: FieldType }
const getFields = () => {
// This would come from a database
const fields: Field[] = [
{ name: 'firstName', type: 'string' },
{ name: 'email', type: 'email' },
{ name: 'age', type: 'int' },
]
return fields
}
const typeSchemas = {
string: z.string(),
email: z.string().email(),
int: z.number().int(),
}
const fieldSchema = (type: FieldType) => typeSchemas[type]
const fieldsSchema = (fields: Field[]) =>
z.object(
fields.reduce(
(obj, field) => ({ ...obj, [field.name]: fieldSchema(field.type) }),
{},
),
)
const mutation = makeDomainFunction(fieldsSchema(getFields()))(
async (values) => values,
)
export function loader(_args: LoaderArgs) {
const fields: Field[] = [
{ name: 'firstName', type: 'string' },
{ name: 'email', type: 'email' },
{ name: 'age', type: 'int' },
]
return { fields }
}
export const action: ActionFunction = async ({ request }) =>
formAction({ request, schema: fieldsSchema(getFields()), mutation })
export default () => <Form schema={fieldsSchema(fields)} />`

type FieldType = 'string' | 'email' | 'int'
type Field = { name: string; type: FieldType }

const getFields = () => {
const fields: Field[] = [
{ name: 'firstName', type: 'string' },
{ name: 'email', type: 'email' },
{ name: 'age', type: 'int' },
]

return fields
}

const typeSchemas = {
string: z.string(),
email: z.string().email(),
int: z.number().int(),
}

const fieldSchema = (type: FieldType) => typeSchemas[type]

const fieldsSchema = (fields: Field[]) =>
z.object(
fields.reduce(
(obj, field) => ({ ...obj, [field.name]: fieldSchema(field.type) }),
{},
),
)

const mutation = makeDomainFunction(fieldsSchema(getFields()))(
async (values) => values,
)

export function loader(_args: LoaderArgs) {
const fields: Field[] = [
{ name: 'firstName', type: 'string' },
{ name: 'email', type: 'email' },
{ name: 'age', type: 'int' },
]

return {
code: hljs.highlight(code, { language: 'ts' }).value,
fields,
}
}

export const action: ActionFunction = async ({ request }) =>
formAction({ request, schema: fieldsSchema(getFields()), mutation })

export default function Component() {
const { fields } = useLoaderData<typeof loader>()

return (
<Example title={title} description={description}>
<Form schema={fieldsSchema(fields)} />
</Example>
)
}

0 comments on commit 9331ea5

Please sign in to comment.