Skip to content

Commit 20dbedf

Browse files
doniyor2109erikras
authored andcommitted
Migrate to new React Context API (#313)
* Migrate to new React Context API * Fix: Include missing context file * Add reactFinalForm prop * Avoid re-rendering Consumer * Tests for finalFormContext * Export hoc and its type definition
1 parent df70345 commit 20dbedf

File tree

9 files changed

+83
-59
lines changed

9 files changed

+83
-59
lines changed

src/Field.js

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
// @flow
22
import * as React from 'react'
3-
import PropTypes from 'prop-types'
43
import { fieldSubscriptionItems } from 'final-form'
54
import diffSubscription from './diffSubscription'
65
import type { FieldSubscription, FieldState } from 'final-form'
7-
import type {
8-
FieldProps as Props,
9-
FieldRenderProps,
10-
ReactContext
11-
} from './types'
6+
import type { FieldProps as Props, FieldRenderProps } from './types'
127
import renderComponent from './renderComponent'
138
import isReactNative from './isReactNative'
149
import getValue from './getValue'
10+
import { withReactFinalForm } from './reactFinalFormContext'
1511

1612
const all: FieldSubscription = fieldSubscriptionItems.reduce((result, key) => {
1713
result[key] = true
@@ -23,32 +19,27 @@ type State = {
2319
}
2420

2521
class Field extends React.Component<Props, State> {
26-
context: ReactContext
2722
props: Props
2823
state: State
2924
unsubscribe: () => void
3025

31-
static contextTypes = {
32-
reactFinalForm: PropTypes.object
33-
}
34-
3526
static defaultProps = {
3627
format: (value: ?any, name: string) => (value === undefined ? '' : value),
3728
parse: (value: ?any, name: string) => (value === '' ? undefined : value)
3829
}
3930

40-
constructor(props: Props, context: ReactContext) {
41-
super(props, context)
31+
constructor(props: Props) {
32+
super(props)
4233
let initialState
4334

4435
// istanbul ignore next
45-
if (process.env.NODE_ENV !== 'production' && !context.reactFinalForm) {
36+
if (process.env.NODE_ENV !== 'production' && !this.props.reactFinalForm) {
4637
console.error(
4738
'Warning: Field must be used inside of a ReactFinalForm component'
4839
)
4940
}
5041

51-
if (this.context.reactFinalForm) {
42+
if (this.props.reactFinalForm) {
5243
// avoid error, warning will alert developer to their mistake
5344
this.subscribe(props, (state: FieldState) => {
5445
if (initialState) {
@@ -65,7 +56,7 @@ class Field extends React.Component<Props, State> {
6556
{ isEqual, name, subscription, validateFields }: Props,
6657
listener: (state: FieldState) => void
6758
) => {
68-
this.unsubscribe = this.context.reactFinalForm.registerField(
59+
this.unsubscribe = this.props.reactFinalForm.registerField(
6960
name,
7061
listener,
7162
subscription || all,
@@ -89,7 +80,7 @@ class Field extends React.Component<Props, State> {
8980
fieldSubscriptionItems
9081
)
9182
) {
92-
if (this.context.reactFinalForm) {
83+
if (this.props.reactFinalForm) {
9384
// avoid error, warning will alert developer to their mistake
9485
this.unsubscribe()
9586
this.subscribe(this.props, this.notify)
@@ -171,6 +162,7 @@ class Field extends React.Component<Props, State> {
171162
subscription,
172163
validate,
173164
validateFields,
165+
reactFinalForm,
174166
value: _value,
175167
...rest
176168
} = this.props
@@ -235,4 +227,4 @@ class Field extends React.Component<Props, State> {
235227
}
236228
}
237229

238-
export default Field
230+
export default withReactFinalForm(Field)

src/FormSpy.js

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,33 @@
11
// @flow
22
import * as React from 'react'
3-
import PropTypes from 'prop-types'
43
import { formSubscriptionItems } from 'final-form'
54
import diffSubscription from './diffSubscription'
65
import renderComponent from './renderComponent'
7-
import type {
8-
FormSpyProps as Props,
9-
FormSpyRenderProps,
10-
ReactContext
11-
} from './types'
6+
import type { FormSpyProps as Props, FormSpyRenderProps } from './types'
127
import type { FormState } from 'final-form'
138
import isSyntheticEvent from './isSyntheticEvent'
149
import { all } from './ReactFinalForm'
10+
import { withReactFinalForm } from './reactFinalFormContext'
1511

1612
type State = { state: FormState }
1713

1814
class FormSpy extends React.Component<Props, State> {
19-
context: ReactContext
2015
props: Props
2116
state: State
2217
unsubscribe: () => void
2318

24-
constructor(props: Props, context: ReactContext) {
25-
super(props, context)
19+
constructor(props: Props) {
20+
super(props)
2621
let initialState
2722

2823
// istanbul ignore next
29-
if (process.env.NODE_ENV !== 'production' && !context.reactFinalForm) {
24+
if (process.env.NODE_ENV !== 'production' && !this.props.reactFinalForm) {
3025
console.error(
3126
'Warning: FormSpy must be used inside of a ReactFinalForm component'
3227
)
3328
}
3429

35-
if (this.context.reactFinalForm) {
30+
if (this.props.reactFinalForm) {
3631
// avoid error, warning will alert developer to their mistake
3732
this.subscribe(props, (state: FormState) => {
3833
if (initialState) {
@@ -54,7 +49,7 @@ class FormSpy extends React.Component<Props, State> {
5449
{ subscription }: Props,
5550
listener: (state: FormState) => void
5651
) => {
57-
this.unsubscribe = this.context.reactFinalForm.subscribe(
52+
this.unsubscribe = this.props.reactFinalForm.subscribe(
5853
listener,
5954
subscription || all
6055
)
@@ -76,7 +71,7 @@ class FormSpy extends React.Component<Props, State> {
7671
formSubscriptionItems
7772
)
7873
) {
79-
if (this.context.reactFinalForm) {
74+
if (this.props.reactFinalForm) {
8075
// avoid error, warning will alert developer to their mistake
8176
this.unsubscribe()
8277
this.subscribe(this.props, this.notify)
@@ -89,8 +84,7 @@ class FormSpy extends React.Component<Props, State> {
8984
}
9085

9186
render() {
92-
const { onChange, subscription, ...rest } = this.props
93-
const { reactFinalForm } = this.context
87+
const { onChange, reactFinalForm, ...rest } = this.props
9488
const renderProps: FormSpyRenderProps = {
9589
batch:
9690
reactFinalForm &&
@@ -197,8 +191,4 @@ class FormSpy extends React.Component<Props, State> {
197191
}
198192
}
199193

200-
FormSpy.contextTypes = {
201-
reactFinalForm: PropTypes.object
202-
}
203-
204-
export default FormSpy
194+
export default withReactFinalForm(FormSpy)

src/ReactFinalForm.js

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// @flow
22
import * as React from 'react'
3-
import PropTypes from 'prop-types'
43
import {
54
configOptions,
65
createForm,
@@ -14,11 +13,13 @@ import type {
1413
FormState,
1514
Unsubscribe
1615
} from 'final-form'
17-
import type { FormProps as Props, ReactContext } from './types'
16+
import type { FormProps as Props } from './types'
1817
import shallowEqual from './shallowEqual'
1918
import renderComponent from './renderComponent'
2019
import isSyntheticEvent from './isSyntheticEvent'
2120
import type { FormRenderProps } from './types.js.flow'
21+
import { ReactFinalFormContext } from './reactFinalFormContext'
22+
2223
export const version = '3.6.0'
2324

2425
const versions = {
@@ -39,18 +40,13 @@ type State = {
3940
}
4041

4142
class ReactFinalForm extends React.Component<Props, State> {
42-
context: ReactContext
4343
props: Props
4444
state: State
4545
form: FormApi
4646
mounted: boolean
4747
resumeValidation: ?boolean
4848
unsubscriptions: Unsubscribe[]
4949

50-
static childContextTypes = {
51-
reactFinalForm: PropTypes.object
52-
}
53-
5450
constructor(props: Props) {
5551
super(props)
5652
const {
@@ -87,12 +83,6 @@ class ReactFinalForm extends React.Component<Props, State> {
8783
}
8884
}
8985

90-
getChildContext() {
91-
return {
92-
reactFinalForm: this.form
93-
}
94-
}
95-
9686
notify = (state: FormState) => {
9787
if (this.mounted) {
9888
this.setState({ state })
@@ -285,13 +275,17 @@ class ReactFinalForm extends React.Component<Props, State> {
285275
return this.form.reset(values)
286276
})
287277
}
288-
return renderComponent(
289-
{
290-
...props,
291-
...renderProps,
292-
__versions: versions
293-
},
294-
'ReactFinalForm'
278+
return React.createElement(
279+
ReactFinalFormContext.Provider,
280+
{ value: this.form },
281+
renderComponent(
282+
{
283+
...props,
284+
...renderProps,
285+
__versions: versions
286+
},
287+
'ReactFinalForm'
288+
)
295289
)
296290
}
297291
}

src/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,7 @@ export var Field: React.ComponentType<FieldProps>
9797
export var Form: React.ComponentType<FormProps>
9898
export var FormSpy: React.ComponentType<FormSpyProps>
9999
export var version: string
100+
101+
export function withReactFinalForm<T>(
102+
component: React.ComponentType<T>
103+
): React.ComponentType<T & ReactContext>

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
export { default as Field } from './Field'
33
export { default as Form, version } from './ReactFinalForm'
44
export { default as FormSpy } from './FormSpy'
5+
export { withReactFinalForm } from './reactFinalFormContext'

src/index.js.flow

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22
import * as React from 'react'
3-
import type { FieldProps, FormProps, FormSpyProps } from './types'
3+
import type { FieldProps, FormProps, FormSpyProps, ReactContext } from './types';
44

55
export type {
66
FieldProps,
@@ -16,3 +16,5 @@ declare export var Field: React.ComponentType<FieldProps>
1616
declare export var Form: React.ComponentType<FormProps>
1717
declare export var FormSpy: React.ComponentType<FormSpyProps>
1818
declare export var version: string
19+
20+
declare export function withReactFinalForm<T>(component: React.ComponentType<T>): React.ComponentType<T & ReactContext>

src/reactFinalFormContext.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as React from 'react'
2+
3+
export const ReactFinalFormContext = React.createContext(null)
4+
5+
export const withReactFinalForm = Component => {
6+
return class extends React.Component {
7+
render() {
8+
return React.createElement(ReactFinalFormContext.Consumer, {
9+
children: reactFinalForm =>
10+
React.createElement(Component, {
11+
reactFinalForm,
12+
...this.props
13+
})
14+
})
15+
}
16+
}
17+
}

src/reactFinalFormContext.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react'
2+
import TestUtils from 'react-dom/test-utils'
3+
4+
import Form from './ReactFinalForm'
5+
import { withReactFinalForm } from './reactFinalFormContext'
6+
7+
describe('reactFinalFormContext', () => {
8+
it('should pass formApi using HOC', () => {
9+
const mockComponent = jest.fn(() => <div />)
10+
const render = () => {
11+
const BoundComponent = withReactFinalForm(mockComponent)
12+
return <BoundComponent />
13+
}
14+
const formComponent = TestUtils.renderIntoDocument(
15+
<Form onSubmit={() => {}} render={render} />
16+
)
17+
expect(mockComponent).toHaveBeenCalled()
18+
expect(mockComponent.mock.calls[0][0].reactFinalForm).toBe(
19+
formComponent.form
20+
)
21+
})
22+
})

src/types.js.flow

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export type FormProps = {
7979
RenderableProps<FormRenderProps>
8080

8181
export type FieldProps = {
82+
reactFinalForm: FormApi,
8283
allowNull?: boolean,
8384
format?: (value: any, name: string) => any,
8485
formatOnBlur?: boolean,
@@ -94,6 +95,7 @@ export type FieldProps = {
9495
}
9596

9697
export type FormSpyProps = {
98+
reactFinalForm: FormApi,
9799
onChange?: (formState: FormState) => void,
98100
subscription?: FormSubscription
99101
} & RenderableProps<FormSpyRenderProps>

0 commit comments

Comments
 (0)