Skip to content

Commit

Permalink
Use accurate Event type
Browse files Browse the repository at this point in the history
The existing Event type would not typecheck for any correct
event-handling code; dataTransfer is not present for non-DnD events, and
value is not guaranteed to be present for non-Input events.

This change is quite safe:

1. Props the user is expected to pass in are replaced with supertypes of
what they were before (a function taking a subtype is a supertype)

2. Props the user is given are replaced with supertypes of what they
were before, but they should only get a type error in cases where they
are legitimately messing up (i.e. are passing an Event that doesn't have
an HTMLInputElement as its target to the handlers in FieldProps).
  • Loading branch information
asazernik committed Feb 8, 2019
1 parent 84359b3 commit 02ec9b3
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 55 deletions.
12 changes: 6 additions & 6 deletions src/ConnectedField.types.js.flow
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import type {Node, Component} from 'react'
import type { Event, Context } from './types'
import type { Context, OptimisticSyntheticDragEvent, OptimisticSyntheticFocusEvent } from './types'

export type InstanceApi = {
name: string,
Expand All @@ -21,16 +21,16 @@ export type Props = {
asyncError: any,
asyncValidating: boolean,
onBlur: {
(event: Event, newValue: ?any, previousValue: ?any, name: string): void
(event: OptimisticSyntheticFocusEvent, newValue: ?any, previousValue: ?any, name: string): void
},
onChange: {
(event: Event, newValue: ?any, previousValue: ?any, name: string): void
(event: SyntheticInputEvent<>, newValue: ?any, previousValue: ?any, name: string): void
},
onDrop: {
(event: Event, newValue: ?any, previousValue: ?any, name: string): void
(event: OptimisticSyntheticDragEvent, newValue: ?any, previousValue: ?any, name: string): void
},
onDragStart: { (event: Event, name: string): void },
onFocus: { (event: Event, name: string): void },
onDragStart: { (event: OptimisticSyntheticDragEvent, name: string): void },
onFocus: { (event: OptimisticSyntheticFocusEvent, name: string): void },
dirty: boolean,
dispatch: { (action: any): void },
form: string,
Expand Down
12 changes: 6 additions & 6 deletions src/ConnectedFields.types.js.flow
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import type { Node } from 'react'
import type { Structure, Event, Context } from './types'
import type { Structure, Context, OptimisticSyntheticDragEvent, OptimisticSyntheticFocusEvent } from './types'

export type Props = {
names: string[],
Expand All @@ -19,11 +19,11 @@ export type Props = {
// validate, warn, parse, format, normalize, etc.
asyncError: any,
asyncValidating: boolean,
onBlur: { (event: Event, newValue: ?any, previousValue: ?any): void },
onChange: { (event: Event, newValue: ?any, previousValue: ?any): void },
onDrop: { (event: Event, newValue: ?any, previousValue: ?any): void },
onDragStart: { (event: Event): void },
onFocus: { (event: Event): void },
onBlur: { (event: OptimisticSyntheticFocusEvent, newValue: ?any, previousValue: ?any): void },
onChange: { (event: SyntheticInputEvent<>, newValue: ?any, previousValue: ?any): void },
onDrop: { (event: OptimisticSyntheticDragEvent, newValue: ?any, previousValue: ?any): void },
onDragStart: { (event: OptimisticSyntheticDragEvent): void },
onFocus: { (event: OptimisticSyntheticFocusEvent): void },
dirty: boolean,
dispatch: { (action: any): void },
form: string,
Expand Down
22 changes: 11 additions & 11 deletions src/FieldProps.types.js.flow
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import type { Dispatch } from 'redux'
import type { Event, Validator } from './types'
import type { OptimisticSyntheticDragEvent, OptimisticSyntheticFocusEvent, Validator } from './types'
import type { ComponentType, ElementRef } from 'react'

export type Props = {
Expand All @@ -15,25 +15,25 @@ export type Props = {
name: string
) => ?any,
onBlur?: (
event: Event,
event: OptimisticSyntheticFocusEvent,
newValue: any,
previousValue: any,
name: string
) => void,
onChange?: (
event: Event,
event: SyntheticInputEvent<>,
newValue: any,
previousValue: any,
name: string
) => void,
onDragStart?: (event: Event, name: string) => void,
onDragStart?: (event: OptimisticSyntheticDragEvent, name: string) => void,
onDrop?: (
event: Event,
event: OptimisticSyntheticDragEvent,
newValue: any,
previousValue: any,
name: string
) => void,
onFocus?: (event: Event, name: string) => void,
onFocus?: (event: OptimisticSyntheticFocusEvent, name: string) => void,
parse?: (value: any, name: string) => any,
props?: Object,
validate?: Validator | Validator[],
Expand All @@ -45,11 +45,11 @@ export type Props = {
export type InputProps = {
checked?: boolean,
name: string,
onBlur: { (eventOrValue: Event | any): void },
onChange: { (eventOrValue: Event | any): void },
onDrop: { (event: Event): void },
onDragStart: { (event: Event): void },
onFocus: { (event: Event): void },
onBlur: { (eventOrValue: OptimisticSyntheticFocusEvent | any): void },
onChange: { (eventOrValue: SyntheticInputEvent<> | any): void },
onDrop: { (event: OptimisticSyntheticDragEvent): void },
onDragStart: { (event: OptimisticSyntheticDragEvent): void },
onFocus: { (event: OptimisticSyntheticFocusEvent): void },
value: any
}

Expand Down
27 changes: 21 additions & 6 deletions src/createFieldProps.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
// @flow
import type { Event, Structure } from './types'
import type { OptimisticSyntheticDragEvent, OptimisticSyntheticFocusEvent, Structure } from './types'
import type { Dispatch } from 'redux'
import type { FieldProps, InputProps } from './FieldProps.types'

export type Props = {
asyncError: any,
asyncValidating: boolean,
onBlur: {
(event: Event, newValue: ?any, previousValue: ?any, name: ?string): void
(
event: OptimisticSyntheticFocusEvent,
newValue: ?any,
previousValue: ?any,
name: ?string
): void
},
onChange: {
(event: Event, newValue: ?any, previousValue: ?any, name: ?string): void
(
event: SyntheticInputEvent<>,
newValue: ?any,
previousValue: ?any,
name: ?string
): void
},
onDrop: {
(event: Event, newValue: ?any, previousValue: ?any, name: ?string): void
(
event: OptimisticSyntheticDragEvent,
newValue: ?any,
previousValue: ?any,
name: ?string
): void
},
onDragStart: { (event: Event, name: ?string): void },
onFocus: { (event: Event, name: ?string): void },
onDragStart: { (event: OptimisticSyntheticDragEvent, name: ?string): void },
onFocus: { (event: OptimisticSyntheticFocusEvent, name: ?string): void },
dirty: boolean,
dispatch: Dispatch<*>,
form: string,
Expand Down
24 changes: 17 additions & 7 deletions src/events/getValue.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import isEvent from './isEvent'
import type { Event } from '../types'
import type { Event, OptimisticSyntheticDragEvent } from '../types'

const getSelectedValues = options => {
const result = []
Expand All @@ -20,26 +20,36 @@ const getValue = (event: Event, isReactNative: ?boolean) => {
if (
!isReactNative &&
event.nativeEvent &&
// $FlowFixMe: react-native has no useful flow types, and this is a compatibility fix for when isReactNative is not properly set
event.nativeEvent.text !== undefined
) {
return event.nativeEvent.text
}
if (isReactNative && event.nativeEvent !== undefined) {
// $FlowFixMe: react-native has no useful flow types
return event.nativeEvent.text
}
const detypedEvent: any = event

const {
target: { type, value, checked, files },
dataTransfer
} = detypedEvent
target: { type, value, checked, files }
} = event
if (type === 'checkbox') {
return !!checked
}
if (type === 'file') {
return files || (dataTransfer && dataTransfer.files)
if (files) {
return files
} else if (event.dataTransfer) {
// events are inexact types, so flow doesn't infer drag event type from existence of event.dataTransfer
const dragEvent: OptimisticSyntheticDragEvent = (event: any)
return dragEvent.dataTransfer.files
} else {
return undefined
}
}
if (type === 'select-multiple') {
return getSelectedValues(event.target.options)
const multiSelect: HTMLSelectElement = (event.target: any)
return getSelectedValues(multiSelect.options)
}
return value
}
Expand Down
35 changes: 16 additions & 19 deletions src/types.js.flow
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @flow
import React from 'react'

export type Action = {
type: string,
Expand Down Expand Up @@ -45,26 +44,24 @@ export type Option = {
value: any
}

export type Event = {
preventDefault(): void,
stopPropagation(): void,
target: {
value: any,
type: string,
options?: Array<Option>,
checked?: boolean,
files?: Array<Object>
},
dataTransfer: {
files: Array<Object>,
getData: { (key: string): any },
setData: { (key: string, data: any): void }
},
nativeEvent?: {
text?: string
}
/*
* React-DOM's default typing of drag and focus events is built for the general case, where the target is not necessarily
* an HTMLInputElement. They can be specialized with a type parameter, but that only affects the currentTarget element,
* not the target. We should maybe be using currentTarget internally, but for backwards-compatibility's sake we'll extend
* these to specify that the target is what we expect
*
* "Optimistic" because we're being optimistic about the browser always doing what we expect
*/
export interface OptimisticSyntheticDragEvent extends SyntheticDragEvent<HTMLInputElement> {
target: HTMLInputElement
}

export interface OptimisticSyntheticFocusEvent extends SyntheticFocusEvent<HTMLInputElement> {
target: HTMLInputElement
}

export type Event = SyntheticInputEvent<> | OptimisticSyntheticDragEvent | OptimisticSyntheticFocusEvent

export type Context = {
form: string,
getFormState: GetFormState,
Expand Down

0 comments on commit 02ec9b3

Please sign in to comment.