Skip to content

Commit 9fe62c5

Browse files
authored
Strongly typed form values for Flow and Typescript (#516)
* Strongly typed form values for Flow and Typescript * Upped version variable and ff version dep
1 parent f349c07 commit 9fe62c5

File tree

13 files changed

+133
-84
lines changed

13 files changed

+133
-84
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"eslint-plugin-react": "^7.13.0",
6262
"eslint-plugin-react-hooks": "^1.6.0",
6363
"fast-deep-equal": "^2.0.1",
64-
"final-form": "^4.13.0",
64+
"final-form": "^4.14.0",
6565
"flow-bin": "^0.98.1",
6666
"glow": "^1.2.2",
6767
"husky": "^2.3.0",
@@ -88,7 +88,7 @@
8888
"typescript": "^3.4.5"
8989
},
9090
"peerDependencies": {
91-
"final-form": "^4.13.0",
91+
"final-form": "^4.14.0",
9292
"react": "^16.8.0"
9393
},
9494
"lint-staged": {

src/FormSpy.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
import * as React from 'react'
33
import renderComponent from './renderComponent'
44
import type { FormSpyPropsWithForm as Props, FormSpyRenderProps } from './types'
5-
import type { FormApi } from 'final-form'
5+
import type { FormApi, FormValuesShape } from 'final-form'
66
import isSyntheticEvent from './isSyntheticEvent'
77
import useFormState from './useFormState'
8-
import ReactFinalFormContext from './context'
8+
import getContext from './getContext'
99

10-
const FormSpy = ({ onChange, subscription, ...rest }: Props) => {
11-
const reactFinalForm: ?FormApi = React.useContext(ReactFinalFormContext)
10+
function FormSpy<FormValues: FormValuesShape>({
11+
onChange,
12+
subscription,
13+
...rest
14+
}: Props<FormValues>) {
15+
const ReactFinalFormContext = getContext<FormValues>()
16+
const reactFinalForm: ?FormApi<FormValues> = React.useContext(
17+
ReactFinalFormContext
18+
)
1219
if (!reactFinalForm) {
1320
throw new Error('FormSpy must be used inside of a ReactFinalForm component')
1421
}
@@ -17,7 +24,7 @@ const FormSpy = ({ onChange, subscription, ...rest }: Props) => {
1724
return null
1825
}
1926

20-
const renderProps: FormSpyRenderProps = {
27+
const renderProps: FormSpyRenderProps<FormValues> = {
2128
form: {
2229
...reactFinalForm,
2330
reset: eventOrValues => {

src/ReactFinalForm.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
Config,
1111
FormSubscription,
1212
FormState,
13+
FormValuesShape,
1314
Unsubscribe
1415
} from 'final-form'
1516
import type { FormProps as Props } from './types'
@@ -19,10 +20,10 @@ import useConstant from './useConstant'
1920
import shallowEqual from './shallowEqual'
2021
import isSyntheticEvent from './isSyntheticEvent'
2122
import type { FormRenderProps } from './types.js.flow'
22-
import ReactFinalFormContext from './context'
23+
import getContext from './getContext'
2324
import useLatest from './useLatest'
2425

25-
export const version = '6.0.1'
26+
export const version = '6.1.0'
2627

2728
const versions = {
2829
'final-form': ffVersion,
@@ -37,7 +38,7 @@ export const all: FormSubscription = formSubscriptionItems.reduce(
3738
{}
3839
)
3940

40-
const ReactFinalForm = ({
41+
function ReactFinalForm<FormValues: FormValuesShape>({
4142
debug,
4243
decorators,
4344
destroyOnUnregister,
@@ -50,8 +51,9 @@ const ReactFinalForm = ({
5051
validate,
5152
validateOnBlur,
5253
...rest
53-
}: Props) => {
54-
const config: Config = {
54+
}: Props<FormValues>) {
55+
const ReactFinalFormContext = getContext<FormValues>()
56+
const config: Config<FormValues> = {
5557
debug,
5658
destroyOnUnregister,
5759
initialValues,
@@ -62,16 +64,16 @@ const ReactFinalForm = ({
6264
validateOnBlur
6365
}
6466

65-
const form: FormApi = useConstant(() => {
66-
const f = createForm(config)
67+
const form: FormApi<FormValues> = useConstant(() => {
68+
const f = createForm<FormValues>(config)
6769
f.pauseValidation()
6870
return f
6971
})
7072

7173
// synchronously register and unregister to query form state for our subscription on first render
72-
const [state, setState] = React.useState<FormState>(
73-
(): FormState => {
74-
let initialState: FormState = {}
74+
const [state, setState] = React.useState<FormState<FormValues>>(
75+
(): FormState<FormValues> => {
76+
let initialState: FormState<FormValues> = {}
7577
form.subscribe(state => {
7678
initialState = state
7779
}, subscription)()
@@ -81,7 +83,7 @@ const ReactFinalForm = ({
8183

8284
// save a copy of state that can break through the closure
8385
// on the shallowEqual() line below.
84-
const stateRef = useLatest<FormState>(state)
86+
const stateRef = useLatest<FormState<FormValues>>(state)
8587

8688
React.useEffect(() => {
8789
// We have rendered, so all fields are no registered, so we can unpause validation
@@ -170,7 +172,7 @@ const ReactFinalForm = ({
170172
return form.submit()
171173
}
172174

173-
const renderProps: FormRenderProps = {
175+
const renderProps: FormRenderProps<FormValues> = {
174176
// assign to force Flow check
175177
...state,
176178
form: {

src/context.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/getContext.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @flow
2+
import * as React from 'react'
3+
import type { FormApi, FormValuesShape } from 'final-form'
4+
5+
let instance: React.Context<?FormApi<any>>
6+
7+
export default function getContext<
8+
FormValues: FormValuesShape
9+
>(): React.Context<FormApi<FormValues>> {
10+
if (!instance) {
11+
instance = React.createContext<?FormApi<FormValues>>()
12+
}
13+
return ((instance: any): React.Context<FormApi<FormValues>>)
14+
}

src/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// @flow
2+
import Form from './ReactFinalForm'
3+
import FormSpy from './FormSpy'
24
export { default as Field } from './Field'
35
export { default as Form, version } from './ReactFinalForm'
46
export { default as FormSpy } from './FormSpy'
57
export { default as useField } from './useField'
68
export { default as useFormState } from './useFormState'
79
export { default as useForm } from './useForm'
8-
export { default as context } from './context'
10+
export function withTypes() {
11+
return { Form, FormSpy }
12+
}

src/index.js.flow

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22
import * as React from 'react'
3-
import type { FormApi, FormState } from 'final-form'
3+
import type { FormApi, FormState, FormValuesShape } from 'final-form'
44
import type {
55
FieldProps,
66
FieldRenderProps,
@@ -20,12 +20,20 @@ export type {
2020
} from './types'
2121

2222
declare export var Field: React.ComponentType<FieldProps>
23-
declare export var Form: React.ComponentType<FormProps>
24-
declare export var FormSpy: React.ComponentType<FormSpyProps>
25-
declare export var useForm: (componentName?: string) => FormApi
26-
declare export var useFormState: UseFormStateParams => ?FormState
23+
declare export var Form: React.ComponentType<FormProps<Object>>
24+
declare export var FormSpy: React.ComponentType<FormSpyProps<Object>>
25+
declare export function useForm<FormValues: FormValuesShape>(
26+
componentName?: string
27+
): FormApi<FormValues>
28+
declare export function useFormState<FormValues>(
29+
params: UseFormStateParams<FormValues>
30+
): ?FormState<FormValues>
2731
declare export var useField: (
2832
name: string,
2933
config: UseFieldConfig
3034
) => FieldRenderProps
35+
declare export function withTypes<FormValues: FormValuesShape>(): {
36+
Form: React.ComponentType<FormProps<FormValues>>,
37+
FormSpy: React.ComponentType<FormSpyProps<FormValues>>
38+
}
3139
declare export var version: string

src/types.js.flow

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import type {
66
Decorator,
77
FormState,
88
FormSubscription,
9+
FormValuesShape,
910
FieldSubscription,
1011
FieldValidator
1112
} from 'final-form'
1213

1314
type SupportedInputs = 'input' | 'select' | 'textarea'
1415

15-
export type ReactContext = {
16-
reactFinalForm: FormApi
16+
export type ReactContext<FormValues: FormValuesShape> = {
17+
reactFinalForm: FormApi<FormValues>
1718
}
1819

1920
export type FieldInputProps = {
@@ -49,27 +50,27 @@ export type FieldRenderProps = {
4950
}
5051
}
5152

52-
export type FormRenderProps = {
53+
export type FormRenderProps<FormValues: FormValuesShape> = {
5354
handleSubmit: (?SyntheticEvent<HTMLFormElement>) => ?Promise<?Object>,
54-
form: FormApi
55-
} & FormState
55+
form: FormApi<FormValues>
56+
} & FormState<FormValues>
5657

57-
export type FormSpyRenderProps = {
58-
form: FormApi
59-
} & FormState
58+
export type FormSpyRenderProps<FormValues: FormValuesShape> = {
59+
form: FormApi<FormValues>
60+
} & FormState<FormValues>
6061

6162
export type RenderableProps<T> = {
6263
component?: React.ComponentType<*> | SupportedInputs,
6364
children?: ((props: T) => React.Node) | React.Node,
6465
render?: (props: T) => React.Node
6566
}
6667

67-
export type FormProps = {
68+
export type FormProps<FormValues: FormValuesShape> = {
6869
subscription?: FormSubscription,
69-
decorators?: Decorator[],
70+
decorators?: Decorator<FormValues>[],
7071
initialValuesEqual?: (?Object, ?Object) => boolean
71-
} & Config &
72-
RenderableProps<FormRenderProps>
72+
} & Config<FormValues> &
73+
RenderableProps<FormRenderProps<FormValues>>
7374

7475
export type UseFieldConfig = {
7576
afterSubmit?: () => void,
@@ -95,14 +96,16 @@ export type FieldProps = UseFieldConfig & {
9596
name: string
9697
} & RenderableProps<FieldRenderProps>
9798

98-
export type UseFormStateParams = {
99-
onChange?: (formState: FormState) => void,
99+
export type UseFormStateParams<FormValues: FormValuesShape> = {
100+
onChange?: (formState: FormState<FormValues>) => void,
100101
subscription?: FormSubscription
101102
}
102103

103-
export type FormSpyProps = UseFormStateParams &
104-
RenderableProps<FormSpyRenderProps>
104+
export type FormSpyProps<
105+
FormValues: FormValuesShape
106+
> = UseFormStateParams<FormValues> &
107+
RenderableProps<FormSpyRenderProps<FormValues>>
105108

106-
export type FormSpyPropsWithForm = {
107-
reactFinalForm: FormApi
108-
} & FormSpyProps
109+
export type FormSpyPropsWithForm<FormValues: FormValuesShape> = {
110+
reactFinalForm: FormApi<FormValues>
111+
} & FormSpyProps<FormValues>

src/useField.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// @flow
22
import * as React from 'react'
33
import { fieldSubscriptionItems } from 'final-form'
4-
import type { FieldSubscription, FieldState, FormApi } from 'final-form'
4+
import type {
5+
FieldSubscription,
6+
FieldState,
7+
FormApi,
8+
FormValuesShape
9+
} from 'final-form'
510
import type { UseFieldConfig, FieldInputProps, FieldRenderProps } from './types'
611
import isReactNative from './isReactNative'
712
import getValue from './getValue'
@@ -18,7 +23,7 @@ const defaultFormat = (value: ?any, name: string) =>
1823
const defaultParse = (value: ?any, name: string) =>
1924
value === '' ? undefined : value
2025

21-
const useField = (
26+
function useField<FormValues: FormValuesShape>(
2227
name: string,
2328
{
2429
afterSubmit,
@@ -38,8 +43,8 @@ const useField = (
3843
validateFields,
3944
value: _value
4045
}: UseFieldConfig = {}
41-
): FieldRenderProps => {
42-
const form: FormApi = useForm('useField')
46+
): FieldRenderProps {
47+
const form: FormApi<FormValues> = useForm<FormValues>('useField')
4348

4449
const validateRef = useLatest(validate)
4550

0 commit comments

Comments
 (0)