Skip to content

Commit

Permalink
fix: createFormFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed May 1, 2023
1 parent 0947516 commit b274bcc
Show file tree
Hide file tree
Showing 10 changed files with 3,673 additions and 343 deletions.
19 changes: 9 additions & 10 deletions examples/react/simple/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import React from "react";
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> }) {
return (
Expand All @@ -14,15 +21,7 @@ function FieldInfo({ field }: { field: FieldApi<any, any> }) {
}

export default function App() {
const form = useForm({
// Memoize your default values to prevent re-renders
defaultValues: React.useMemo(
() => ({
firstName: "",
lastName: "",
}),
[]
),
const form = formFactory.useForm({
onSubmit: async (values) => {
// Do something with form data
console.log(values);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"stream-to-array": "^2.3.0",
"ts-jest": "^27.1.1",
"ts-node": "^10.7.0",
"typescript": "^4.7.4",
"typescript": "^5.0.4",
"vue": "^3.2.33"
},
"bundlewatch": {
Expand Down
15 changes: 8 additions & 7 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import { Store } from '@tanstack/store'

export type ValidationCause = 'change' | 'blur' | 'submit'

export type FieldOptions<TData, TFormData> = {
export interface FieldOptions<TData, TFormData> {
name: unknown extends TFormData ? string : DeepKeys<TFormData>
defaultValue?: TData
form?: FormApi<TFormData>
validate?: (
value: TData,
fieldApi: FieldApi<TData, TFormData>,
Expand All @@ -24,6 +23,13 @@ export type FieldOptions<TData, TFormData> = {
defaultMeta?: Partial<FieldMeta>
}

export type FieldApiOptions<TData, TFormData> = FieldOptions<
TData,
TFormData
> & {
form: FormApi<TFormData>
}

export type FieldMeta = {
isTouched: boolean
touchedError?: ValidationError
Expand Down Expand Up @@ -53,11 +59,6 @@ export type InputProps = {
onBlur: (event: any) => void
}

export type FieldApiOptions<TData, TFormData> = RequiredByKey<
FieldOptions<TData, TFormData>,
'form'
>

let uid = 0

export type FieldState<TData> = {
Expand Down
5 changes: 2 additions & 3 deletions packages/react-form/src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
type DeepValue,
type FieldApi,
type FieldOptions,
type FormApi,
} from '@tanstack/form-core'
import { useField } from './useField'

Expand All @@ -19,9 +18,9 @@ export type FieldComponent<TFormData> = <TField extends DeepKeys<TFormData>>({
name: TField
} & Omit<FieldOptions<DeepValue<TFormData, TField>, TFormData>, 'name'>) => any

export function createFieldComponent<TFormData>(formApi: FormApi<TFormData>) {
export function createFieldComponent<TFormData>() {
const ConnectedField: FieldComponent<TFormData> = (props) => (
<Field {...(props as any)} form={formApi} />
<Field {...(props as any)} />
)
return ConnectedField
}
Expand Down
19 changes: 19 additions & 0 deletions packages/react-form/src/createFormFactory.ts
Original file line number Diff line number Diff line change
@@ -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(),
}
}
6 changes: 1 addition & 5 deletions packages/react-form/src/formContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import * as React from 'react'

export const formContext = React.createContext<FormApi<any> | null>(null)

export function useFormContext(customFormApi?: FormApi<any>) {
export function useFormContext() {
const formApi = React.useContext(formContext)

if (customFormApi) {
return customFormApi
}

if (!formApi) {
throw new Error(`You are trying to use the form API outside of a form!`)
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-form/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from '@tanstack/form-core'
export * from './useForm'
export * from './Field'
export * from './useField'
export * from './createFormFactory'
30 changes: 13 additions & 17 deletions packages/react-form/src/useField.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as React from 'react'
//
import { useStore } from '@tanstack/react-store'
import type {
DeepKeys,
DeepValue,
FieldOptions,
FormApi,
} from '@tanstack/form-core'
import type { DeepKeys, DeepValue, FieldOptions } from '@tanstack/form-core'
import { FieldApi } from '@tanstack/form-core'
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>>(
opts?: { name: TField } & FieldOptions<
Expand All @@ -17,26 +20,19 @@ export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
>,
) => FieldApi<DeepValue<TFormData, TField>, TFormData>

export function createUseField<TFormData>(formApi: FormApi<TFormData>) {
const useFormField: UseField<TFormData> = (opts) => {
return useField({ ...opts, form: formApi } as any)
export function createUseField<TFormData>(): UseField<TFormData> {
return (opts) => {
return useField(opts as any)
}

return useFormField
}

export function useField<TData, TFormData>(
opts: FieldOptions<TData, TFormData> & {
// selector: (state: FieldApi<TData, TFormData>) => TSelected
},
): 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
const formApi = useFormContext(opts.form)
const formApi = useFormContext()

const [fieldApi] = React.useState<FieldApi<TData, TFormData>>(
() => new FieldApi({ ...opts, form: formApi }),
Expand Down
13 changes: 6 additions & 7 deletions packages/react-form/src/useForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,21 @@ declare module '@tanstack/form-core' {
}) => any
}
}
//

export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
const [formApi] = React.useState(() => {
// @ts-ignore
const api = new FormApi<TData>(opts || {})
const api = new FormApi<TData>(opts)

api.Form = createFormComponent(api)
api.Field = createFieldComponent(api)
api.useField = createUseField(api)
api.Field = createFieldComponent<TData>()
api.useField = createUseField<TData>()
api.useStore = (
// @ts-ignore
selector,
) => {
// 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 = (
// @ts-ignore
Expand All @@ -48,7 +47,7 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
return functionalUpdate(
props.children,
// eslint-disable-next-line react-hooks/rules-of-hooks
useStore(api.store, props.selector),
useStore(api.store, props.selector as any),
) as any
}

Expand All @@ -57,7 +56,7 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {

// React.useEffect(() => formApi.mount(), [])

return formApi
return formApi as any
}

export type FormProps = React.HTMLProps<HTMLFormElement> & {
Expand Down
Loading

0 comments on commit b274bcc

Please sign in to comment.