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
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

✅ Opt-in subscriptions - only update on the state you need!

✅ 💥 **2.2k gzipped** 💥
✅ 💥 **2.5k gzipped** 💥

---

Expand Down Expand Up @@ -177,8 +177,9 @@ const MyForm = () => (
* [`FormSpyProps`](#formspyprops)
* [`children?: ((props: FormSpyRenderProps) => React.Node) | React.Node`](#children-props-formspyrenderprops--reactnode--reactnode)
* [`component?: React.ComponentType<FormSpyRenderProps>`](#component-reactcomponenttypeformspyrenderprops)
* [`onChange?: (formState: FormState) => void`](#onchange-formstate-formstate--void)
* [`render?: (props: FormSpyRenderProps) => React.Node`](#render-props-formspyrenderprops--reactnode)
* [`formSubscription?: FormSubscription`](#formsubscription-formsubscription)
* [`subscription?: FormSubscription`](#subscription-formsubscription-1)
* [`FormSpyRenderProps`](#formspyrenderprops)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -538,19 +539,28 @@ of the ways to render: `component`, `render`, or `children`.
#### `children?: ((props: FormSpyRenderProps) => React.Node) | React.Node`

A render function that is given [`FormSpyRenderProps`](#formspyrenderprops), as
well as any non-API props passed into the `<FormSpy/>` component.
well as any non-API props passed into the `<FormSpy/>` component. Will not be
called if an `onChange` prop is provided.

#### `component?: React.ComponentType<FormSpyRenderProps>`

A component that is given [`FormSpyRenderProps`](#formspyrenderprops) as props,
as well as any non-API props passed into the `<FormSpy/>` component.
as well as any non-API props passed into the `<FormSpy/>` component. Will not be
called if an `onChange` prop is provided.

#### `onChange?: (formState: FormState) => void`

A change listener that will be called with form state whenever the form state,
as subscribed to by the `subscription` prop, has changed. When an `onChange`
prop is provided, the `<FormSpy/>` will not render anything.

#### `render?: (props: FormSpyRenderProps) => React.Node`

A render function that is given [`FormSpyRenderProps`](#formspyrenderprops), as
well as any non-API props passed into the `<FormSpy/>` component.
well as any non-API props passed into the `<FormSpy/>` component. Will not be
called if an `onChange` prop is provided.

#### `formSubscription?: FormSubscription`
#### `subscription?: FormSubscription`

A
[`FormSubscription`](https://github.com/final-form/final-form#formsubscription--string-boolean-)
Expand Down
2 changes: 1 addition & 1 deletion docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ and you'd like it to be), please correct the mistake.

| Feature | [🏁 React-Final-Form](https://github.com/final-form/react-final-form#-react-final-form) | [Formik](https://github.com/jaredpalmer/formik) | [Redux-Form](https://github.com/erikras/redux-form) |
| ------------------------------------- | :---------------------------------------------------------------------------------------------------------------: | :----------------------------------------------: | :---------------------------------------------------: |
| Bundle Size | [3.5k](https://bundlephobia.com/result?p=final-form) + [2.2k](https://bundlephobia.com/result?p=react-final-form) | [7.5k](https://bundlephobia.com/result?p=formik) | [26.8k](https://bundlephobia.com/result?p=redux-form) |
| Bundle Size | [3.5k](https://bundlephobia.com/result?p=final-form) + [2.5k](https://bundlephobia.com/result?p=react-final-form) | [7.5k](https://bundlephobia.com/result?p=formik) | [26.8k](https://bundlephobia.com/result?p=redux-form) |
| Works without Redux | ✅ | ✅ | ❌ |
| Record-Level Sync Validation | ✅ | ✅ | ✅ |
| Record-Level Async Validation | ✅ | ✅ | ✅ |
Expand Down
16 changes: 13 additions & 3 deletions src/FormSpy.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export default class FormSpy extends React.PureComponent<Props, State> {
this.notify(state)
} else {
initialState = state
if (props.onChange) {
props.onChange(state)
}
}
})
}
Expand All @@ -47,7 +50,12 @@ export default class FormSpy extends React.PureComponent<Props, State> {
)
}

notify = (state: FormState) => this.setState({ state })
notify = (state: FormState) => {
this.setState({ state })
if (this.props.onChange) {
this.props.onChange(state)
}
}

componentWillReceiveProps(nextProps: Props) {
const { subscription } = nextProps
Expand All @@ -71,8 +79,10 @@ export default class FormSpy extends React.PureComponent<Props, State> {
}

render() {
const { subscription, ...rest } = this.props
return renderComponent({ ...rest, ...this.state.state }, 'FormSpy')
const { onChange, subscription, ...rest } = this.props
return onChange
? null
: renderComponent({ ...rest, ...this.state.state }, 'FormSpy')
}
}

Expand Down
87 changes: 87 additions & 0 deletions src/FormSpy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,91 @@ describe('FormSpy', () => {
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
})

it('should call onChange', () => {
const input = jest.fn(({ input }) => <input {...input} />)
const onChange = jest.fn()
TestUtils.renderIntoDocument(
<Form onSubmit={onSubmitMock}>
{() => (
<form>
<FormSpy subscription={{ dirty: true }} onChange={onChange} />
<Field name="foo" render={input} />
</form>
)}
</Form>
)
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalled()
expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith({ dirty: false })

input.mock.calls[0][0].input.onChange('bar')

expect(input).toHaveBeenCalledTimes(2)
expect(onChange).toHaveBeenCalledTimes(2)
expect(onChange).toHaveBeenCalledWith({ dirty: true })
})

it('should not render with render prop when given onChange', () => {
const onChange = jest.fn()
const render = jest.fn()
TestUtils.renderIntoDocument(
<Form onSubmit={onSubmitMock}>
{() => (
<form>
<FormSpy
subscription={{ dirty: true }}
onChange={onChange}
render={render}
/>
</form>
)}
</Form>
)
expect(onChange).toHaveBeenCalled()
expect(onChange).toHaveBeenCalledTimes(1)
expect(render).not.toHaveBeenCalled()
})

it('should not render with child render prop when given onChange', () => {
const onChange = jest.fn()
const render = jest.fn()
TestUtils.renderIntoDocument(
<Form onSubmit={onSubmitMock}>
{() => (
<form>
<FormSpy subscription={{ dirty: true }} onChange={onChange}>
{render}
</FormSpy>
</form>
)}
</Form>
)
expect(onChange).toHaveBeenCalled()
expect(onChange).toHaveBeenCalledTimes(1)
expect(render).not.toHaveBeenCalled()
})

it('should not render with component prop when given onChange', () => {
const onChange = jest.fn()
const render = jest.fn()
TestUtils.renderIntoDocument(
<Form onSubmit={onSubmitMock}>
{() => (
<form>
<FormSpy
subscription={{ dirty: true }}
onChange={onChange}
component={render}
/>
</form>
)}
</Form>
)
expect(onChange).toHaveBeenCalled()
expect(onChange).toHaveBeenCalledTimes(1)
expect(render).not.toHaveBeenCalled()
})
})
3 changes: 2 additions & 1 deletion src/types.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type FormRenderProps = {
reset: () => void
} & FormState

export type FormSpyRenderProps = {} & FormState
export type FormSpyRenderProps = FormState

export type RenderableProps<T> = $Shape<{
children: ((props: T) => React.Node) | React.Node,
Expand All @@ -68,5 +68,6 @@ export type FieldProps = {
} & RenderableProps<FieldRenderProps>

export type FormSpyProps = {
onChange?: (formState: FormState) => void,
subscription?: FormSubscription
} & RenderableProps<FormSpyRenderProps>