Skip to content

Commit

Permalink
refactor(Select): Refactor Downshift items to be strings
Browse files Browse the repository at this point in the history
This refactors Select so that Downshift holds items as an array of strings rather than an array of ReactElements.

This brings it in line with recent changes to Autocomplete.
  • Loading branch information
jpveooys committed Sep 23, 2022
1 parent 7515f0a commit 2f0719a
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 71 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'
import React from 'react'
import {
IconAgriculture,
IconAnchor,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { isNil } from 'lodash'
import React, { useCallback } from 'react'
import { useCombobox } from 'downshift'

import { itemToString, SelectBaseProps, SelectLayout } from '../SelectBase'
import {
getSelectedItem,
itemToString,
SelectBaseProps,
SelectLayout,
} from '../SelectBase'
import { NoResults } from './NoResults'
import { useHighlightedIndex } from './hooks/useHighlightedIndex'
import { ItemsMap, useAutocomplete } from './hooks/useAutocomplete'
import { useAutocomplete } from './hooks/useAutocomplete'
import { useMenuVisibility } from '../SelectBase/hooks/useMenuVisibility'
import { useExternalId } from '../../hooks/useExternalId'
import { useToggleButton } from './hooks/useToggleButton'
Expand All @@ -26,10 +30,6 @@ export interface AutocompleteProps extends SelectBaseProps {
initialValue?: string | null
}

function getSelectedItem(value: string | null | undefined, itemsMap: ItemsMap) {
return !isNil(value) && value in itemsMap ? value : null
}

export const Autocomplete: React.FC<AutocompleteProps> = ({
children,
id: externalId,
Expand Down Expand Up @@ -72,16 +72,12 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
isOpen = false,
openMenu,
reset,
selectedItem,
setHighlightedIndex,
setInputValue,
} = useCombobox<string>({
initialIsOpen,
items: filteredItems.map((item) => item.props.value),
itemToString: (itemValue) =>
!isNil(itemValue) && itemValue in itemsMap
? itemsMap[itemValue].props.children
: '',
itemToString: (item) => itemToString(item, itemsMap),
onInputValueChange,
onIsOpenChange,
onSelectedItemChange: (changes) => {
Expand Down Expand Up @@ -152,7 +148,6 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
},
ref: buttonRef,
})}
value={selectedItem ? itemToString(selectedItem) : ''}
{...rest}
>
{isOpen &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import { useCombobox, UseComboboxStateChange } from 'downshift'

import { findIndexOfInputValue } from '../helpers'
import {
ItemsMap,
SelectBaseOptionAsStringProps,
SelectChildWithStringType,
} from '../../SelectBase'

export type ItemsMap = {
[id: string]: React.ReactElement<SelectBaseOptionAsStringProps>
}

export function useAutocomplete(children: SelectChildWithStringType[]): {
filteredItems: React.ReactElement<SelectBaseOptionAsStringProps>[]
hasError: boolean
Expand Down
64 changes: 30 additions & 34 deletions packages/react-component-library/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useRef } from 'react'
import { useSelect } from 'downshift'
import { isNil } from 'lodash'

import {
initialSelectedItem,
getSelectedItem,
itemToString,
SelectBaseOptionAsStringProps,
SelectBaseProps,
SelectChildWithStringType,
SelectLayout,
} from '../SelectBase'
import { useExternalId } from '../../hooks/useExternalId'
Expand All @@ -23,6 +23,15 @@ export const Select: React.FC<SelectBaseProps> = ({
}) => {
const id = useExternalId('select', externalId)
const inputRef = useRef<HTMLInputElement>(null)

const filteredItems = React.Children.toArray(children).filter(
React.isValidElement<SelectBaseOptionAsStringProps>
)
const items = filteredItems.map((child) => child.props.value)
const itemsMap = Object.fromEntries(
filteredItems.map((item) => [item.props.value, item])
)

const {
getItemProps,
getMenuProps,
Expand All @@ -32,19 +41,13 @@ export const Select: React.FC<SelectBaseProps> = ({
openMenu,
reset,
selectedItem,
} = useSelect<SelectChildWithStringType>({
itemToString,
} = useSelect<string>({
itemToString: (item) => itemToString(item, itemsMap),
initialIsOpen,
initialSelectedItem: initialSelectedItem(children, value),
items: React.Children.toArray(children),
initialSelectedItem: getSelectedItem(value, itemsMap),
items,
onSelectedItemChange: ({ selectedItem: newItem }) => {
if (onChange) {
onChange(
React.isValidElement<SelectBaseOptionAsStringProps>(newItem)
? newItem.props.value
: null
)
}
onChange?.(newItem ?? null)
},
})

Expand All @@ -54,7 +57,7 @@ export const Select: React.FC<SelectBaseProps> = ({

return (
<SelectLayout
hasSelectedItem={!!selectedItem}
hasSelectedItem={!isNil(selectedItem)}
id={id}
inputProps={{
onFocus: onInputFocusHandler,
Expand All @@ -68,32 +71,25 @@ export const Select: React.FC<SelectBaseProps> = ({
reset()
}}
toggleButtonProps={getToggleButtonProps()}
value={selectedItem ? itemToString(selectedItem) : ''}
value={isNil(selectedItem) ? '' : itemsMap[selectedItem].props.children}
{...{
readOnly: true,
...rest,
}}
>
{isOpen &&
React.Children.map(
children,
(child: SelectChildWithStringType, index) => {
if (!React.isValidElement<SelectBaseOptionAsStringProps>(child)) {
return null
}

return React.cloneElement(child, {
...child.props,
...getItemProps({
index,
item: child,
key: `select-option-${child.props.children}`,
}),
isHighlighted: highlightedIndex === index,
title: child.props.children,
})
}
)}
filteredItems.map((child, index) => {
return React.cloneElement(child, {
...child.props,
...getItemProps({
index,
item: child.props.value,
key: `select-option-${child.props.value}`,
}),
isHighlighted: highlightedIndex === index,
title: child.props.children,
})
})}
</SelectLayout>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import React from 'react'
import { isNil } from 'lodash'

import { SelectBaseOptionAsStringProps } from './SelectBaseOption'
import { SelectChildrenType, SelectChildWithStringType } from './types'
import { ItemsMap } from './types'

function itemToString(item: SelectChildWithStringType) {
return React.isValidElement<SelectBaseOptionAsStringProps>(item)
? item.props.children
: ''
function getSelectedItem(value: string | null | undefined, itemsMap: ItemsMap) {
return !isNil(value) && value in itemsMap ? value : null
}

function initialSelectedItem(
children: SelectChildrenType,
value: string | null
) {
if (value === null) {
return null
}

return React.Children.toArray(children).find((child) => {
return React.isValidElement(child) ? child.props.value === value : null
})
function itemToString(item: string | null, itemsMap: ItemsMap) {
return !isNil(item) && item in itemsMap ? itemsMap[item].props.children : ''
}

export { initialSelectedItem, itemToString }
export { getSelectedItem, itemToString }
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export type SelectChildWithStringType =
export type SelectChildrenType =
| SelectChildType<SelectBaseOptionProps>
| SelectChildType<SelectBaseOptionProps>[]

export type ItemsMap = {
[id: string]: React.ReactElement<SelectBaseOptionAsStringProps>
}

0 comments on commit 2f0719a

Please sign in to comment.