Skip to content

Commit

Permalink
fix: select does not return the correct "value" (#276)
Browse files Browse the repository at this point in the history
* fix: select does not return the correct "value"
* select onChange returns the chosen `option`'s `value` or `label`
as first argument and the complete `option`'s `object` as second argument.
If the value is created, the first argument will be the label,
otherwise, it will be the value.

Fixes #230
  • Loading branch information
P1X3L authored and anucreative committed Aug 20, 2019
1 parent 971efb1 commit 8188f30
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 37 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"lodash.isequal": "=4.5.0",
"lodash.kebabcase": "=4.1.1",
"lodash.merge": "^4.6.1",
"lodash.reject": "=4.6.0",
"lodash.uniqby": "=4.7.0",
"match-sorter": "=3.1.1",
"polished": "^3.4.1",
Expand Down
12 changes: 10 additions & 2 deletions src/components/ConnectedField/doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { RadioTab } from '../RadioTab'
import { Select } from '../Select'
import { FileUpload } from '../FileUpload'
import { DateTimePicker } from '../DateTimePicker'
import kebabCase from 'lodash.kebabcase'

# ConnectedField

Expand Down Expand Up @@ -79,10 +80,16 @@ You can find additional props for each field component in [Fields](/fields/file-
dogs: true,
hungry: true,
weekday: 'monday',
month: { label: 'February', value: 'february' },
tags: [],
month: 'february',
colors: ['red', 'blue'],
partyDate: Date.now()
}
// Add newly-created values to options
handleColorCreate = value => {
if (!colors.find(color => color.value === value)) {
colors.push({ value: kebabCase(value), label: value })
}
}
// Render
return (
<Form
Expand Down Expand Up @@ -199,6 +206,7 @@ You can find additional props for each field component in [Fields](/fields/file-
isSearchable
isCreatable
label="Colors"
onCreate={handleColorCreate}
mb="xl"
/>
<ConnectedField
Expand Down
2 changes: 1 addition & 1 deletion src/components/MarkdownEditor/doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ We use [react-simplemde-editor](https://github.com/RIP21/react-simplemde-editor)

You can send an array of toolbar items with the shape:

`{ name: 'bold', icon: <i className="fa fa-bold" />, action={(item) => {console.debug(item)}} }`.
`{ name: 'bold', icon: <i className="fa fa-bold" />, action={item => { // Do something with item }} }`.

For example, in the toolbar below we're using icons from:

Expand Down
35 changes: 13 additions & 22 deletions src/components/Select/doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ It is based on the [downshift](https://github.com/downshift-js/downshift) librar
{ value: 'vue', label: 'Vue' }
]
const [value, setValue] = useState()
const handleChange = e => setValue(e.target.value)
const handleChange = value => setValue(value)
return <Select options={ITEMS} name="default" onChange={handleChange} value={value} />
}}
</Playground>
Expand All @@ -47,8 +47,8 @@ Just add the `isMultiple` prop. Note: to be able to choose multiple values you m
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => setValue(e.target.value)
// you can pass either the label, the value or the entire object
const [value, setValue] = useState(['React', 'ember', ITEMS[2]])
return (
<Select isMultiple options={ITEMS} name="multiple" onChange={console.debug} value={value} />
)
Expand All @@ -70,7 +70,7 @@ To be able to filter (i.e. search) the results, add the `isSearchable` prop.
{ value: 'vue', label: 'Vue' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => setValue(e.target.value)
const handleChange = value => setValue(value)
return (
<Select
isSearchable
Expand Down Expand Up @@ -98,9 +98,7 @@ You can normally clear the selected value of a `Select` but if it has a `require
{ value: 'vue', label: 'Vue' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => {
setValue(e.target.value)
}
const handleChange = value => setValue(value)
return <Select required options={ITEMS} name="required" onChange={handleChange} value={value} />
}}
</Playground>
Expand All @@ -124,9 +122,7 @@ Passing a `renderItem` function allows you to format the options in the list (an
{ value: 'youtube', label: 'Youtube' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => {
setValue(e.target.value)
}
const handleChange = value => setValue(value)
return (
<Select
required
Expand Down Expand Up @@ -160,10 +156,8 @@ If your field is searchable (i.e. has the `isSearchable` prop, you can _add_ ite
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => setValue(e.target.value)
return (
<Select isCreatable isSearchable options={ITEMS} name="creatable" onChange={handleChange} />
<Select isCreatable isSearchable options={ITEMS} name="creatable" onChange={console.debug} />
)
}}
</Playground>
Expand All @@ -186,21 +180,20 @@ You can pass any combination of the props above. For example below, we have a `S
{ value: 'xing', label: 'Xing' },
{ value: 'youtube', label: 'Youtube' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => setValue(e.target.value)
return (
<Select
isCreatable
isMultiple
isSearchable
options={ITEMS}
name="creatable"
onChange={handleChange}
onChange={console.debug}
renderItem={option => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Icon name={option.value} size="sm" mr="xs" /> <span>{option.label}</span>
</div>
)}
value={['Facebook', 'Twitter']}
/>
)
}}
Expand All @@ -220,8 +213,8 @@ Use size property with `sm` `md` or `lg`(default).
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' }
]
const [values, setValues] = useState({ small: ITEMS[2], medium: ITEMS[1], large: ITEMS[0] })
const handleChange = e => setValues({ ...values, [e.target.name]: e.target.value })
const [values, setValues] = useState({ small: 'elm', medium: 'backbone', large: 'angular' })
const handleChange = (value, e) => setValues({ ...values, [e.target.name]: value })
return (
<>
<Select
Expand Down Expand Up @@ -263,10 +256,8 @@ Use size property with `sm` `md` or `lg`(default).
{ value: 'vue', label: 'Vue' }
]
const [value, setValue] = useState(ITEMS[2])
const handleChange = e => setValue(e.target.value)
return (
<Select disabled options={ITEMS} name="disabled" onChange={handleChange} value={ITEMS[3]} />
)
const handleChange = value => setValue(value)
return <Select disabled options={ITEMS} name="disabled" onChange={handleChange} value="ember" />
}}
</Playground>

Expand Down
56 changes: 46 additions & 10 deletions src/components/Select/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { forwardRef, useEffect, useState } from 'react'
import React, { forwardRef, useCallback, useEffect, useState } from 'react'
import { arrayOf, bool, func, oneOfType, string } from 'prop-types'
import Downshift from 'downshift'
import matchSorter from 'match-sorter'
import kebabCase from 'lodash.kebabcase'
import uniqBy from 'lodash.uniqby'
import isEqual from 'lodash.isequal'
import reject from 'lodash.reject'

import { OPTIONS_TYPE, SIZES_TYPE, VARIANTS_TYPE } from '../../utils'
import { Icon } from '../Icon'
Expand All @@ -16,11 +17,26 @@ import * as S from './styles'
// Helpers
const EMPTY = ''
const itemToString = item => (item ? item.label : '')
const ensureArray = value => (Array.isArray(value) ? value : value ? [value] : [])
const getUniqueValue = (item, values) => uniqBy([...values, item], item => item.value)
const isValueExisting = (value, values) =>
values.find(item => kebabCase(item.value) === kebabCase(value))
const defaultRenderOption = option => (option ? option.label : EMPTY)
const findOption = (value, options) => {
const option = options.find(
option => option.label === (value.label || value) || option.value === (value.value || value)
)
// Create the option if it doesn't exist
return option || { value: kebabCase(option), label: option }
}
const optionFromValue = (options, value) => {
if (!value) {
return []
} else if (Array.isArray(value)) {
return value.map(value => findOption(value, options))
} else {
return [findOption(value, options)]
}
}

export const Select = forwardRef(
(
Expand All @@ -35,6 +51,7 @@ export const Select = forwardRef(
name,
onBlur,
onChange,
onCreate,
onFocus,
onKeyDown,
placeholder = 'Choose from…',
Expand All @@ -47,19 +64,21 @@ export const Select = forwardRef(
},
ref
) => {
const selectedItem = (!isMultiple && defaultValue) || null
const defaultInputValue = selectedItem ? defaultValue.label : EMPTY
const getOptionFromValue = useCallback(value => optionFromValue(options, value), [options])
const defaultOption = getOptionFromValue(defaultValue)
const selectedItem = (!isMultiple && defaultOption && defaultOption[0]) || null
const defaultInputValue = selectedItem ? defaultOption[0] : EMPTY
// Values will always be an array internally
const [values, setValues] = useState(ensureArray(defaultValue))
const [values, setValues] = useState(defaultOption)
const [inputValue, setInputValue] = useState(defaultInputValue)
const [results, setResults] = useState(options)

// Ensure values are controlled by parent
useEffect(() => {
setValues(ensureArray(defaultValue))
setValues(getOptionFromValue(defaultValue))
setInputValue(defaultInputValue)
setResults(options)
}, [defaultValue, defaultInputValue, options])
}, [defaultValue, defaultInputValue, getOptionFromValue, options])

// Update results if searchable
const handleInputChange = (value, openMenu) => {
Expand All @@ -78,11 +97,27 @@ export const Select = forwardRef(
}
}

const getValue = value => {
if (!value) return
const getCorrectValue = value =>
isValueExisting(value.value, options) ? value.value : value.label
return Array.isArray(value) ? value.map(getCorrectValue) : getCorrectValue(value)
}

// Send event to parent when value(s) changes
const handleChange = values => {
const value = isMultiple ? values : values[0]
const event = createEvent({ name, value })
onChange && onChange(event)
onChange && onChange(getValue(value), event)
if (isCreatable) {
// If there are newly-created values, call `onCreate`
const created = reject(values, value =>
options.find(option => option.value === value.value)
)
if (created.length) {
onCreate && onCreate(created[0].label, event)
}
}
}

// Update internal state when clicking/adding a select item
Expand Down Expand Up @@ -227,7 +262,7 @@ export const Select = forwardRef(
{renderItem(item)}
</S.Item>
))}
{isShowCreate && (
{isShowCreate && inputValue.length && (
<S.Item
key="add"
{...getItemProps({
Expand Down Expand Up @@ -273,6 +308,7 @@ Select.propTypes = {
name: string,
onBlur: func,
onChange: func,
onCreate: func,
onFocus: func,
onKeyDown: func,
options: arrayOf(OPTIONS_TYPE),
Expand All @@ -281,6 +317,6 @@ Select.propTypes = {
required: bool,
searchable: bool,
size: SIZES_TYPE,
value: oneOfType([OPTIONS_TYPE, arrayOf(OPTIONS_TYPE)]),
value: oneOfType([OPTIONS_TYPE, arrayOf(OPTIONS_TYPE)], string),
variant: VARIANTS_TYPE
}
4 changes: 2 additions & 2 deletions src/utils/events.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const createEvent = ({ name, value }) => ({
export const createEvent = props => ({
preventDefault: () => {},
target: { name, value }
target: { ...props }
})
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8016,6 +8016,11 @@ lodash.merge@^4.0.2, lodash.merge@^4.6.1:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==

lodash.reject@=4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415"
integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=

lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
Expand Down

1 comment on commit 8188f30

@vercel
Copy link

@vercel vercel bot commented on 8188f30 Aug 20, 2019

Choose a reason for hiding this comment

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

Please sign in to comment.