Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-react-hooks": "^1.6.0",
"fast-deep-equal": "^2.0.1",
"final-form": "^4.13.0",
"final-form": "^4.14.0",
"flow-bin": "^0.98.1",
"glow": "^1.2.2",
"husky": "^2.3.0",
Expand All @@ -88,7 +88,7 @@
"typescript": "^3.4.5"
},
"peerDependencies": {
"final-form": "^4.13.0",
"final-form": "^4.14.0",
"react": "^16.8.0"
},
"lint-staged": {
Expand Down
17 changes: 12 additions & 5 deletions src/FormSpy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
import * as React from 'react'
import renderComponent from './renderComponent'
import type { FormSpyPropsWithForm as Props, FormSpyRenderProps } from './types'
import type { FormApi } from 'final-form'
import type { FormApi, FormValuesShape } from 'final-form'
import isSyntheticEvent from './isSyntheticEvent'
import useFormState from './useFormState'
import ReactFinalFormContext from './context'
import getContext from './getContext'

const FormSpy = ({ onChange, subscription, ...rest }: Props) => {
const reactFinalForm: ?FormApi = React.useContext(ReactFinalFormContext)
function FormSpy<FormValues: FormValuesShape>({
onChange,
subscription,
...rest
}: Props<FormValues>) {
const ReactFinalFormContext = getContext<FormValues>()
const reactFinalForm: ?FormApi<FormValues> = React.useContext(
ReactFinalFormContext
)
if (!reactFinalForm) {
throw new Error('FormSpy must be used inside of a ReactFinalForm component')
}
Expand All @@ -17,7 +24,7 @@ const FormSpy = ({ onChange, subscription, ...rest }: Props) => {
return null
}

const renderProps: FormSpyRenderProps = {
const renderProps: FormSpyRenderProps<FormValues> = {
form: {
...reactFinalForm,
reset: eventOrValues => {
Expand Down
26 changes: 14 additions & 12 deletions src/ReactFinalForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Config,
FormSubscription,
FormState,
FormValuesShape,
Unsubscribe
} from 'final-form'
import type { FormProps as Props } from './types'
Expand All @@ -19,10 +20,10 @@ import useConstant from './useConstant'
import shallowEqual from './shallowEqual'
import isSyntheticEvent from './isSyntheticEvent'
import type { FormRenderProps } from './types.js.flow'
import ReactFinalFormContext from './context'
import getContext from './getContext'
import useLatest from './useLatest'

export const version = '6.0.1'
export const version = '6.1.0'

const versions = {
'final-form': ffVersion,
Expand All @@ -37,7 +38,7 @@ export const all: FormSubscription = formSubscriptionItems.reduce(
{}
)

const ReactFinalForm = ({
function ReactFinalForm<FormValues: FormValuesShape>({
debug,
decorators,
destroyOnUnregister,
Expand All @@ -50,8 +51,9 @@ const ReactFinalForm = ({
validate,
validateOnBlur,
...rest
}: Props) => {
const config: Config = {
}: Props<FormValues>) {
const ReactFinalFormContext = getContext<FormValues>()
const config: Config<FormValues> = {
debug,
destroyOnUnregister,
initialValues,
Expand All @@ -62,16 +64,16 @@ const ReactFinalForm = ({
validateOnBlur
}

const form: FormApi = useConstant(() => {
const f = createForm(config)
const form: FormApi<FormValues> = useConstant(() => {
const f = createForm<FormValues>(config)
f.pauseValidation()
return f
})

// synchronously register and unregister to query form state for our subscription on first render
const [state, setState] = React.useState<FormState>(
(): FormState => {
let initialState: FormState = {}
const [state, setState] = React.useState<FormState<FormValues>>(
(): FormState<FormValues> => {
let initialState: FormState<FormValues> = {}
form.subscribe(state => {
initialState = state
}, subscription)()
Expand All @@ -81,7 +83,7 @@ const ReactFinalForm = ({

// save a copy of state that can break through the closure
// on the shallowEqual() line below.
const stateRef = useLatest<FormState>(state)
const stateRef = useLatest<FormState<FormValues>>(state)

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

const renderProps: FormRenderProps = {
const renderProps: FormRenderProps<FormValues> = {
// assign to force Flow check
...state,
form: {
Expand Down
5 changes: 0 additions & 5 deletions src/context.js

This file was deleted.

14 changes: 14 additions & 0 deletions src/getContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @flow
import * as React from 'react'
import type { FormApi, FormValuesShape } from 'final-form'

let instance: React.Context<?FormApi<any>>

export default function getContext<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erikras what's the reason behind this? kinda surprising, is this lazy evaluation needed for anything?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about lazy, it's about typing. I couldn't figure a way to handle the typing.

I suppose I could've just cast the thing...

FormValues: FormValuesShape
>(): React.Context<FormApi<FormValues>> {
if (!instance) {
instance = React.createContext<?FormApi<FormValues>>()
}
return ((instance: any): React.Context<FormApi<FormValues>>)
}
7 changes: 5 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// @flow
import Form from './ReactFinalForm'
import FormSpy from './FormSpy'
export { default as Field } from './Field'
export { default as Form, version } from './ReactFinalForm'
export { default as FormSpy } from './FormSpy'
export { default as ReactFinalFormContext } from './context'
export { default as useField } from './useField'
export { default as useFormState } from './useFormState'
export { default as useForm } from './useForm'
export { default as context } from './context'
export function withTypes() {
return { Form, FormSpy }
}
18 changes: 13 additions & 5 deletions src/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import * as React from 'react'
import type { FormApi, FormState } from 'final-form'
import type { FormApi, FormState, FormValuesShape } from 'final-form'
import type {
FieldProps,
FieldRenderProps,
Expand All @@ -20,12 +20,20 @@ export type {
} from './types'

declare export var Field: React.ComponentType<FieldProps>
declare export var Form: React.ComponentType<FormProps>
declare export var FormSpy: React.ComponentType<FormSpyProps>
declare export var useForm: (componentName?: string) => FormApi
declare export var useFormState: UseFormStateParams => ?FormState
declare export var Form: React.ComponentType<FormProps<Object>>
declare export var FormSpy: React.ComponentType<FormSpyProps<Object>>
declare export function useForm<FormValues: FormValuesShape>(
componentName?: string
): FormApi<FormValues>
declare export function useFormState<FormValues>(
params: UseFormStateParams<FormValues>
): ?FormState<FormValues>
declare export var useField: (
name: string,
config: UseFieldConfig
) => FieldRenderProps
declare export function withTypes<FormValues: FormValuesShape>(): {
Form: React.ComponentType<FormProps<FormValues>>,
FormSpy: React.ComponentType<FormSpyProps<FormValues>>
}
declare export var version: string
41 changes: 22 additions & 19 deletions src/types.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import type {
Decorator,
FormState,
FormSubscription,
FormValuesShape,
FieldSubscription,
FieldValidator
} from 'final-form'

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

export type ReactContext = {
reactFinalForm: FormApi
export type ReactContext<FormValues: FormValuesShape> = {
reactFinalForm: FormApi<FormValues>
}

export type FieldInputProps = {
Expand Down Expand Up @@ -49,27 +50,27 @@ export type FieldRenderProps = {
}
}

export type FormRenderProps = {
export type FormRenderProps<FormValues: FormValuesShape> = {
handleSubmit: (?SyntheticEvent<HTMLFormElement>) => ?Promise<?Object>,
form: FormApi
} & FormState
form: FormApi<FormValues>
} & FormState<FormValues>

export type FormSpyRenderProps = {
form: FormApi
} & FormState
export type FormSpyRenderProps<FormValues: FormValuesShape> = {
form: FormApi<FormValues>
} & FormState<FormValues>

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

export type FormProps = {
export type FormProps<FormValues: FormValuesShape> = {
subscription?: FormSubscription,
decorators?: Decorator[],
decorators?: Decorator<FormValues>[],
initialValuesEqual?: (?Object, ?Object) => boolean
} & Config &
RenderableProps<FormRenderProps>
} & Config<FormValues> &
RenderableProps<FormRenderProps<FormValues>>

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

export type UseFormStateParams = {
onChange?: (formState: FormState) => void,
export type UseFormStateParams<FormValues: FormValuesShape> = {
onChange?: (formState: FormState<FormValues>) => void,
subscription?: FormSubscription
}

export type FormSpyProps = UseFormStateParams &
RenderableProps<FormSpyRenderProps>
export type FormSpyProps<
FormValues: FormValuesShape
> = UseFormStateParams<FormValues> &
RenderableProps<FormSpyRenderProps<FormValues>>

export type FormSpyPropsWithForm = {
reactFinalForm: FormApi
} & FormSpyProps
export type FormSpyPropsWithForm<FormValues: FormValuesShape> = {
reactFinalForm: FormApi<FormValues>
} & FormSpyProps<FormValues>
13 changes: 9 additions & 4 deletions src/useField.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// @flow
import * as React from 'react'
import { fieldSubscriptionItems } from 'final-form'
import type { FieldSubscription, FieldState, FormApi } from 'final-form'
import type {
FieldSubscription,
FieldState,
FormApi,
FormValuesShape
} from 'final-form'
import type { UseFieldConfig, FieldInputProps, FieldRenderProps } from './types'
import isReactNative from './isReactNative'
import getValue from './getValue'
Expand All @@ -18,7 +23,7 @@ const defaultFormat = (value: ?any, name: string) =>
const defaultParse = (value: ?any, name: string) =>
value === '' ? undefined : value

const useField = (
function useField<FormValues: FormValuesShape>(
name: string,
{
afterSubmit,
Expand All @@ -38,8 +43,8 @@ const useField = (
validateFields,
value: _value
}: UseFieldConfig = {}
): FieldRenderProps => {
const form: FormApi = useForm('useField')
): FieldRenderProps {
const form: FormApi<FormValues> = useForm<FormValues>('useField')

const validateRef = useLatest(validate)

Expand Down
11 changes: 7 additions & 4 deletions src/useForm.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// @flow
import * as React from 'react'
import type { FormApi } from 'final-form'
import ReactFinalFormContext from './context'
import type { FormApi, FormValuesShape } from 'final-form'
import getContext from './getContext'

const useForm = (componentName?: string): FormApi => {
const form: ?FormApi = React.useContext(ReactFinalFormContext)
function useForm<FormValues: FormValuesShape>(
componentName?: string
): FormApi<FormValues> {
const ReactFinalFormContext = getContext<FormValues>()
const form: ?FormApi<FormValues> = React.useContext(ReactFinalFormContext)
if (!form) {
throw new Error(
`${componentName || 'useForm'} must be used inside of a <Form> component`
Expand Down
14 changes: 7 additions & 7 deletions src/useFormState.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// @flow
import * as React from 'react'
import type { UseFormStateParams } from './types'
import type { FormState, FormApi } from 'final-form'
import type { FormState, FormApi, FormValuesShape } from 'final-form'
import { all } from './ReactFinalForm'
import useForm from './useForm'

const useFormState = ({
function useFormState<FormValues: FormValuesShape>({
onChange,
subscription = all
}: UseFormStateParams = {}): FormState => {
const form: FormApi = useForm('useFormState')
}: UseFormStateParams<FormValues> = {}): FormState<FormValues> {
const form: FormApi<FormValues> = useForm<FormValues>('useFormState')
const firstRender = React.useRef(true)

// synchronously register and unregister to query field state for our subscription on first render
const [state, setState] = React.useState<FormState>(
(): FormState => {
let initialState: FormState = {}
const [state, setState] = React.useState<FormState<FormValues>>(
(): FormState<FormValues> => {
let initialState: FormState<FormValues> = {}
form.subscribe(state => {
initialState = state
}, subscription)()
Expand Down
Loading