mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-06 12:27:45 +00:00
fix: createFormFactory
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { useForm, FieldApi } from "@tanstack/react-form";
|
import { FieldApi, createFormFactory } from "@tanstack/react-form";
|
||||||
|
|
||||||
|
const formFactory = createFormFactory({
|
||||||
|
defaultValues: {
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function FieldInfo({ field }: { field: FieldApi<any, any> }) {
|
function FieldInfo({ field }: { field: FieldApi<any, any> }) {
|
||||||
return (
|
return (
|
||||||
@@ -14,15 +21,7 @@ function FieldInfo({ field }: { field: FieldApi<any, any> }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const form = useForm({
|
const form = formFactory.useForm({
|
||||||
// Memoize your default values to prevent re-renders
|
|
||||||
defaultValues: React.useMemo(
|
|
||||||
() => ({
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
// Do something with form data
|
// Do something with form data
|
||||||
console.log(values);
|
console.log(values);
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
"stream-to-array": "^2.3.0",
|
"stream-to-array": "^2.3.0",
|
||||||
"ts-jest": "^27.1.1",
|
"ts-jest": "^27.1.1",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^5.0.4",
|
||||||
"vue": "^3.2.33"
|
"vue": "^3.2.33"
|
||||||
},
|
},
|
||||||
"bundlewatch": {
|
"bundlewatch": {
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import { Store } from '@tanstack/store'
|
|||||||
|
|
||||||
export type ValidationCause = 'change' | 'blur' | 'submit'
|
export type ValidationCause = 'change' | 'blur' | 'submit'
|
||||||
|
|
||||||
export type FieldOptions<TData, TFormData> = {
|
export interface FieldOptions<TData, TFormData> {
|
||||||
name: unknown extends TFormData ? string : DeepKeys<TFormData>
|
name: unknown extends TFormData ? string : DeepKeys<TFormData>
|
||||||
defaultValue?: TData
|
defaultValue?: TData
|
||||||
form?: FormApi<TFormData>
|
|
||||||
validate?: (
|
validate?: (
|
||||||
value: TData,
|
value: TData,
|
||||||
fieldApi: FieldApi<TData, TFormData>,
|
fieldApi: FieldApi<TData, TFormData>,
|
||||||
@@ -24,6 +23,13 @@ export type FieldOptions<TData, TFormData> = {
|
|||||||
defaultMeta?: Partial<FieldMeta>
|
defaultMeta?: Partial<FieldMeta>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FieldApiOptions<TData, TFormData> = FieldOptions<
|
||||||
|
TData,
|
||||||
|
TFormData
|
||||||
|
> & {
|
||||||
|
form: FormApi<TFormData>
|
||||||
|
}
|
||||||
|
|
||||||
export type FieldMeta = {
|
export type FieldMeta = {
|
||||||
isTouched: boolean
|
isTouched: boolean
|
||||||
touchedError?: ValidationError
|
touchedError?: ValidationError
|
||||||
@@ -53,11 +59,6 @@ export type InputProps = {
|
|||||||
onBlur: (event: any) => void
|
onBlur: (event: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldApiOptions<TData, TFormData> = RequiredByKey<
|
|
||||||
FieldOptions<TData, TFormData>,
|
|
||||||
'form'
|
|
||||||
>
|
|
||||||
|
|
||||||
let uid = 0
|
let uid = 0
|
||||||
|
|
||||||
export type FieldState<TData> = {
|
export type FieldState<TData> = {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
type DeepValue,
|
type DeepValue,
|
||||||
type FieldApi,
|
type FieldApi,
|
||||||
type FieldOptions,
|
type FieldOptions,
|
||||||
type FormApi,
|
|
||||||
} from '@tanstack/form-core'
|
} from '@tanstack/form-core'
|
||||||
import { useField } from './useField'
|
import { useField } from './useField'
|
||||||
|
|
||||||
@@ -19,9 +18,9 @@ export type FieldComponent<TFormData> = <TField extends DeepKeys<TFormData>>({
|
|||||||
name: TField
|
name: TField
|
||||||
} & Omit<FieldOptions<DeepValue<TFormData, TField>, TFormData>, 'name'>) => any
|
} & Omit<FieldOptions<DeepValue<TFormData, TField>, TFormData>, 'name'>) => any
|
||||||
|
|
||||||
export function createFieldComponent<TFormData>(formApi: FormApi<TFormData>) {
|
export function createFieldComponent<TFormData>() {
|
||||||
const ConnectedField: FieldComponent<TFormData> = (props) => (
|
const ConnectedField: FieldComponent<TFormData> = (props) => (
|
||||||
<Field {...(props as any)} form={formApi} />
|
<Field {...(props as any)} />
|
||||||
)
|
)
|
||||||
return ConnectedField
|
return ConnectedField
|
||||||
}
|
}
|
||||||
|
|||||||
19
packages/react-form/src/createFormFactory.ts
Normal file
19
packages/react-form/src/createFormFactory.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { FormApi, FormOptions } from '@tanstack/form-core'
|
||||||
|
import { createUseField, type UseField } from './useField'
|
||||||
|
import { useForm } from './useForm'
|
||||||
|
|
||||||
|
export type FormFactory<TFormData> = {
|
||||||
|
useForm: (opts?: FormOptions<TFormData>) => FormApi<TFormData>
|
||||||
|
useField: UseField<TFormData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFormFactory<TFormData>(
|
||||||
|
defaultOpts?: FormOptions<TFormData>,
|
||||||
|
): FormFactory<TFormData> {
|
||||||
|
return {
|
||||||
|
useForm: (opts) => {
|
||||||
|
return useForm<TFormData>({ ...defaultOpts, ...opts } as any) as any
|
||||||
|
},
|
||||||
|
useField: createUseField(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,9 @@ import * as React from 'react'
|
|||||||
|
|
||||||
export const formContext = React.createContext<FormApi<any> | null>(null)
|
export const formContext = React.createContext<FormApi<any> | null>(null)
|
||||||
|
|
||||||
export function useFormContext(customFormApi?: FormApi<any>) {
|
export function useFormContext() {
|
||||||
const formApi = React.useContext(formContext)
|
const formApi = React.useContext(formContext)
|
||||||
|
|
||||||
if (customFormApi) {
|
|
||||||
return customFormApi
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formApi) {
|
if (!formApi) {
|
||||||
throw new Error(`You are trying to use the form API outside of a form!`)
|
throw new Error(`You are trying to use the form API outside of a form!`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export * from '@tanstack/form-core'
|
|||||||
export * from './useForm'
|
export * from './useForm'
|
||||||
export * from './Field'
|
export * from './Field'
|
||||||
export * from './useField'
|
export * from './useField'
|
||||||
|
export * from './createFormFactory'
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
//
|
//
|
||||||
import { useStore } from '@tanstack/react-store'
|
import { useStore } from '@tanstack/react-store'
|
||||||
import type {
|
import type { DeepKeys, DeepValue, FieldOptions } from '@tanstack/form-core'
|
||||||
DeepKeys,
|
|
||||||
DeepValue,
|
|
||||||
FieldOptions,
|
|
||||||
FormApi,
|
|
||||||
} from '@tanstack/form-core'
|
|
||||||
import { FieldApi } from '@tanstack/form-core'
|
import { FieldApi } from '@tanstack/form-core'
|
||||||
import { useFormContext } from './formContext'
|
import { useFormContext } from './formContext'
|
||||||
|
import type { FormFactory } from './createFormFactory'
|
||||||
|
|
||||||
|
declare module '@tanstack/form-core' {
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
interface FieldOptions<TData, TFormData> {
|
||||||
|
formFactory?: FormFactory<TFormData>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
|
export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
|
||||||
opts?: { name: TField } & FieldOptions<
|
opts?: { name: TField } & FieldOptions<
|
||||||
@@ -17,12 +20,10 @@ export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
|
|||||||
>,
|
>,
|
||||||
) => FieldApi<DeepValue<TFormData, TField>, TFormData>
|
) => FieldApi<DeepValue<TFormData, TField>, TFormData>
|
||||||
|
|
||||||
export function createUseField<TFormData>(formApi: FormApi<TFormData>) {
|
export function createUseField<TFormData>(): UseField<TFormData> {
|
||||||
const useFormField: UseField<TFormData> = (opts) => {
|
return (opts) => {
|
||||||
return useField({ ...opts, form: formApi } as any)
|
return useField(opts as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
return useFormField
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useField<TData, TFormData>(
|
export function useField<TData, TFormData>(
|
||||||
@@ -30,13 +31,8 @@ export function useField<TData, TFormData>(
|
|||||||
// selector: (state: FieldApi<TData, TFormData>) => TSelected
|
// selector: (state: FieldApi<TData, TFormData>) => TSelected
|
||||||
},
|
},
|
||||||
): FieldApi<TData, TFormData> {
|
): FieldApi<TData, TFormData> {
|
||||||
// invariant( // TODO:
|
|
||||||
// opts.name,
|
|
||||||
// `useField: A field is required to use this hook. eg, useField('myField', options)`
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Get the form API either manually or from context
|
// Get the form API either manually or from context
|
||||||
const formApi = useFormContext(opts.form)
|
const formApi = useFormContext()
|
||||||
|
|
||||||
const [fieldApi] = React.useState<FieldApi<TData, TFormData>>(
|
const [fieldApi] = React.useState<FieldApi<TData, TFormData>>(
|
||||||
() => new FieldApi({ ...opts, form: formApi }),
|
() => new FieldApi({ ...opts, form: formApi }),
|
||||||
|
|||||||
@@ -24,22 +24,21 @@ declare module '@tanstack/form-core' {
|
|||||||
}) => any
|
}) => any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
|
|
||||||
export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
||||||
const [formApi] = React.useState(() => {
|
const [formApi] = React.useState(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const api = new FormApi<TData>(opts || {})
|
const api = new FormApi<TData>(opts)
|
||||||
|
|
||||||
api.Form = createFormComponent(api)
|
api.Form = createFormComponent(api)
|
||||||
api.Field = createFieldComponent(api)
|
api.Field = createFieldComponent<TData>()
|
||||||
api.useField = createUseField(api)
|
api.useField = createUseField<TData>()
|
||||||
api.useStore = (
|
api.useStore = (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
selector,
|
selector,
|
||||||
) => {
|
) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
return useStore(api.store, selector) as any
|
return useStore(api.store, selector as any) as any
|
||||||
}
|
}
|
||||||
api.Subscribe = (
|
api.Subscribe = (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -48,7 +47,7 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
|||||||
return functionalUpdate(
|
return functionalUpdate(
|
||||||
props.children,
|
props.children,
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
useStore(api.store, props.selector),
|
useStore(api.store, props.selector as any),
|
||||||
) as any
|
) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
|||||||
|
|
||||||
// React.useEffect(() => formApi.mount(), [])
|
// React.useEffect(() => formApi.mount(), [])
|
||||||
|
|
||||||
return formApi
|
return formApi as any
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormProps = React.HTMLProps<HTMLFormElement> & {
|
export type FormProps = React.HTMLProps<HTMLFormElement> & {
|
||||||
|
|||||||
3860
pnpm-lock.yaml
generated
3860
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user