Skip to content

Commit

Permalink
Add the option suppressRefError to getRootProps.
Browse files Browse the repository at this point in the history
  • Loading branch information
yp committed Oct 24, 2017
1 parent f1460bf commit dbd9215
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 4 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ but differ slightly.

> `function(inputValue: string, stateAndHelpers: object)` | optional, no useful default
Called whenever the input value changes. Useful to use instead or in combination of `onStateChange` when `inputValue` is a controlled prop to [avoid issues with cursor positions](https://github.com/paypal/downshift/issues/217).
Called whenever the input value changes. Useful to use instead or in combination of `onStateChange` when `inputValue` is a controlled prop to [avoid issues with cursor positions](https://github.com/paypal/downshift/issues/217).

- `inputValue`: The current value of the input
- `stateAndHelpers`: This is the same thing your `children` prop
Expand Down Expand Up @@ -400,7 +400,7 @@ being overridden (or overriding the props returned). For example:
| `getInputProps` | `function({})` | returns the props you should apply to the `input` element that you render. |
| `getItemProps` | `function({})` | returns the props you should apply to any menu item elements you render. |
| `getLabelProps` | `function({})` | returns the props you should apply to the `label` element that you render. |
| `getRootProps` | `function({})` | returns the props you should apply to the root element that you render. It can be optional. |
| `getRootProps` | `function({},{})` | returns the props you should apply to the root element that you render. It can be optional. |

#### `getRootProps`

Expand All @@ -418,6 +418,17 @@ Required properties:
and your composite component would forward like:
`<div ref={props.innerRef} />`

If you're rendering a composite component, `Downshift` checks that
`getRootProps` is called and that `refKey` is a prop of the returned composite
component.
This is done to catch common causes of errors but, in some cases, the check
could fail even if the ref is correctly forwarded to the root DOM component.
In these cases, you can provide the object `{suppressRefError : true}` as the
second argument to `getRootProps` to completely bypass the check.
** Please use it with extreme care and only if you are absolutely sure that the
ref is correctly forwarded otherwise `Downshift` will unexpectedly fail. **
See issue #235 for the discussion that lead to this.

#### `getInputProps`

This method should be applied to the `input` you render. It is recommended that
Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/downshift.get-root-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,51 @@ test('renders fine when rendering a composite component and applying getRootProp
expect(() => mount(<MyComponent />)).not.toThrow()
})

test('returning a composite component and calling getRootProps without a refKey does not result in an error if suppressRefError is true', () => {
const MyComponent = () => (
<Downshift>
{({getRootProps}) => (
<MyDiv {...getRootProps({}, {suppressRefError: true})} />
)}
</Downshift>
)
expect(() => mount(<MyComponent />)).not.toThrow()
})

test('returning a DOM element and calling getRootProps with a refKey does not result in an error if suppressRefError is true', () => {
const MyComponent = () => (
<Downshift>
{({getRootProps}) => (
<div {...getRootProps({refKey: 'blah'}, {suppressRefError: true})} />
)}
</Downshift>
)
expect(() => mount(<MyComponent />)).not.toThrow()
})

test('not applying the ref prop results in an error does not result in an error if suppressRefError is true', () => {
const MyComponent = () => (
<Downshift>
{({getRootProps}) => {
const {onClick} = getRootProps({}, {suppressRefError: true})
return <div onClick={onClick} />
}}
</Downshift>
)
expect(() => mount(<MyComponent />)).not.toThrow()
})

test('renders fine when rendering a composite component and applying getRootProps properly even if suppressRefError is true', () => {
const MyComponent = () => (
<Downshift>
{({getRootProps}) => (
<MyDiv
{...getRootProps({refKey: 'innerRef'}, {suppressRefError: true})}
/>
)}
</Downshift>
)
expect(() => mount(<MyComponent />)).not.toThrow()
})

/* eslint no-console:0 */
11 changes: 9 additions & 2 deletions src/downshift.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,11 +424,15 @@ class Downshift extends Component {

rootRef = node => (this._rootNode = node)

getRootProps = ({refKey = 'ref', ...rest} = {}) => {
getRootProps = (
{refKey = 'ref', ...rest} = {},
{suppressRefError = false} = {},
) => {
// this is used in the render to know whether the user has called getRootProps.
// It uses that to know whether to apply the props automatically
this.getRootProps.called = true
this.getRootProps.refKey = refKey
this.getRootProps.suppressRefError = suppressRefError
return {
[refKey]: this.rootRef,
...rest,
Expand Down Expand Up @@ -767,6 +771,7 @@ class Downshift extends Component {
// apply the props for them.
this.getRootProps.called = false
this.getRootProps.refKey = undefined
this.getRootProps.suppressRefError = undefined
// we do something similar for getLabelProps
this.getLabelProps.called = false
// and something similar for getInputProps
Expand All @@ -776,7 +781,9 @@ class Downshift extends Component {
return null
}
if (this.getRootProps.called) {
validateGetRootPropsCalledCorrectly(element, this.getRootProps)
if (!this.getRootProps.suppressRefError) {
validateGetRootPropsCalledCorrectly(element, this.getRootProps)
}
return element
} else if (isDOMElement(element)) {
// they didn't apply the root props, but we can clone
Expand Down

0 comments on commit dbd9215

Please sign in to comment.