Open1

Blitz.jsで使うテンプレコードメモ

YuheiNakasakaYuheiNakasaka

Form

app/pages/playgrounds/my_form_page.tsx

import { Head, BlitzPage } from "blitz"
import { Flex, Box, Text } from "@chakra-ui/react"
import Layout from "app/core/layouts/Layout"
import MyForm from "app/playgrounds/components/MyFormComponent"
import {
MyFormContextProvider,
MyFormContextType,
MyFormContext,
} from "app/playgrounds/context/MyFormContext"
import { useContext } from "react"

const MyFormMain = () => {
const context: MyFormContextType = useContext(MyFormContext)
return (
<>
<Head>
<title>Title</title>
</Head>
<Flex bg="white" w="100vw">
<Flex as="header" position="fixed" top={0} width="full" py={4} px={8}>
<MyForm
submitText="検索"
initialValues={{ text: "" }}
onSubmit={(value) => {
alert(Value: ${JSON.stringify(context.text)})
}}
></MyForm>
</Flex>
</Flex>
</>
)
}

const MyFormPage: BlitzPage = () => {
return (
<MyFormContextProvider>
<MyFormMain></MyFormMain>
</MyFormContextProvider>
)
}

MyFormPage.authenticate = false
MyFormPage.getLayout = (page) => <Layout>{page}</Layout>

export default MyFormPage

app/playgrounds/components/MyFormComponent.tsx
import {
  ReactNode,
  forwardRef,
  ComponentPropsWithoutRef,
  PropsWithoutRef,
  useContext,
  ChangeEvent,
} from "react"
import {
  Form as FinalForm,
  FormProps as FinalFormProps,
  useField,
  UseFieldConfig,
} from "react-final-form"
import { Flex, Button, Input } from "@chakra-ui/react"
import { FormControl, FormLabel } from "@chakra-ui/form-control"
import { validateZodSchema } from "blitz"
import { z } from "zod"
import { MyFormContext, MyFormContextType } from "../context/MyFormContext"

const FormSchema = z.object({
  text: z.string(),
})

export interface MyFormProps extends ComponentPropsWithoutRef<typeof Input> {
  name: string
  label: string
  type?: "text" | "number"
  outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
  labelProps?: ComponentPropsWithoutRef<"label">
  fieldProps?: UseFieldConfig<string>
}

interface FormProps<S extends z.ZodType<any, any>>
  extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
  children?: ReactNode
  submitText?: string
  schema?: S
  onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
  initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
}

function Form<S extends z.ZodType<any, any>>({
  children,
  submitText,
  initialValues,
  onSubmit,
  ...props
}: FormProps<S>) {
  return (
    <FinalForm
      initialValues={initialValues}
      validate={validateZodSchema(FormSchema)}
      onSubmit={onSubmit}
      render={({ handleSubmit, submitting, submitError }) => (
        <form onSubmit={handleSubmit} className="form" {...props}>
          <Flex>
            {submitError && (
              <div role="alert" style={{ color: "red" }}>
                {submitError}
              </div>
            )}
            {children}
            {submitText && (
              <Button type="submit" disabled={submitting}>
                {submitText}
              </Button>
            )}
          </Flex>
        </form>
      )}
    />
  )
}

const MyForm = forwardRef<HTMLInputElement, MyFormProps>(
  ({ name, label, outerProps, fieldProps, labelProps, ...props }, ref) => {
    const context: MyFormContextType = useContext(MyFormContext)
    const {
      input,
      meta: { touched, error, submitError, submitting },
    } = useField(name, {
      parse: props.type === "number" ? (Number as any) : (v) => (v === "" ? null : v),
      ...fieldProps,
    })

    const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError

    return (
      <FormControl {...outerProps}>
        <FormLabel {...labelProps}>
          {label}
          <Input
            {...input}
            disabled={submitting}
            {...props}
            ref={ref}
            value={context.text}
            onChange={(e: ChangeEvent<HTMLInputElement>) => {
              context.setText(e.target.value)
            }}
          />
        </FormLabel>
        {touched && normalizedError && (
          <div role="alert" style={{ color: "red" }}>
            {normalizedError}
          </div>
        )}
      </FormControl>
    )
  }
)

export function MyFormConponent<S extends z.ZodType<any, any>>(props: FormProps<S>) {
  return (
    <Form<S> {...props} style={{ width: "100%" }}>
      <MyForm name="text" label="" placeholder="今日は何があった?" />
    </Form>
  )
}

export default MyFormConponent
app/playgrounds/context/MyFormContext.tsx
import * as React from "react"

export interface MyFormContextType {
  text: string
  setText: (t: string) => void
}

export const MyFormContext = React.createContext<MyFormContextType>({
  text: "",
  setText: (text: string) => {},
})

export const MyFormContextProvider: React.FC = ({ children }) => {
  const context: MyFormContextType = React.useContext(MyFormContext)
  const [text, setText] = React.useState(context.text)
  const newContext: MyFormContextType = {
    text,
    setText,
  }
  return <MyFormContext.Provider value={newContext}>{children}</MyFormContext.Provider>
}