mirror of
https://github.com/fergalmoran/turnstone.git
synced 2025-12-22 09:49:56 +00:00
Add useItemsState hook
Store props in state Enforce minimum of 1 for minQueryLength prop
This commit is contained in:
@@ -3,7 +3,7 @@ import setify from 'setify' // Sets input value without changing cursor position
|
||||
import { StateContext } from '../context/state'
|
||||
import Items from './items'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { useAutoFocus, useQueryChange } from './hooks/containerEffects'
|
||||
import { useItemsState, useAutoFocus, useQueryChange } from './hooks/containerEffects'
|
||||
import useData from './hooks/useData'
|
||||
import undef from '../utils/undef'
|
||||
import isUndefined from '../utils/isUndefined'
|
||||
@@ -11,7 +11,6 @@ import startsWithCaseInsensitive from '../utils/startsWithCaseInsensitive'
|
||||
import defaultStyles from './styles/input.styles.js'
|
||||
import {
|
||||
setQuery,
|
||||
setItems,
|
||||
setHighlighted,
|
||||
clearHighlighted,
|
||||
highlightPrev,
|
||||
@@ -22,24 +21,24 @@ import {
|
||||
export default function Container(props) {
|
||||
// Destructure props
|
||||
const {
|
||||
autoFocus = false,
|
||||
debounceWait = 250,
|
||||
autoFocus,
|
||||
debounceWait,
|
||||
defaultItemGroups,
|
||||
defaultItemGroupsAreImmutable = true,
|
||||
defaultItemGroupsAreImmutable,
|
||||
displayField,
|
||||
data,
|
||||
dataSearchType,
|
||||
isDisabled = false,
|
||||
itemGroupsAreImmutable = true,
|
||||
maxItems = 10,
|
||||
minQueryLength = 0,
|
||||
isDisabled,
|
||||
itemGroupsAreImmutable,
|
||||
maxItems,
|
||||
minQueryLength,
|
||||
loadingMessage,
|
||||
noItemsMessage,
|
||||
onChange,
|
||||
onSelect,
|
||||
onEnter,
|
||||
onTab,
|
||||
placeholder = ''
|
||||
placeholder
|
||||
} = props
|
||||
|
||||
// Destructure itemGroups prop
|
||||
@@ -62,7 +61,7 @@ export default function Container(props) {
|
||||
// Component state
|
||||
const [debouncedQuery] = useDebounce(state.query, debounceWait)
|
||||
const [hasFocus, setHasFocus] = useState(false)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// DOM references
|
||||
const queryInput = useRef(null)
|
||||
@@ -87,10 +86,8 @@ export default function Container(props) {
|
||||
dispatch
|
||||
).data
|
||||
|
||||
// Store retrieved data in global state
|
||||
useEffect(() => {
|
||||
dispatch(setItems(swrData || []))
|
||||
}, [swrData])
|
||||
// Store retrieved data in global state as state.items
|
||||
useItemsState(swrData)
|
||||
|
||||
// Autofocus on render if prop is true
|
||||
useAutoFocus(queryInput, autoFocus)
|
||||
@@ -99,19 +96,11 @@ export default function Container(props) {
|
||||
// typeahead value and the query value and fire onChnage
|
||||
useQueryChange(state.query, queryInput, typeaheadInput, onChange)
|
||||
|
||||
// // Whenever the dropdown items change, set the highlighted item
|
||||
// // to either the first or nothing if there are no items
|
||||
// useEffect(() => {
|
||||
// setHighlightedState(
|
||||
// state.items && state.items.length ? { index: 0, text: state.items[0].text } : undef
|
||||
// )
|
||||
// }, [state.items, setHighlightedState])
|
||||
|
||||
// Figure out whether we are able to display a loading state //TODO: useReducer instead of useeffect?
|
||||
// Figure out whether we are able to display a loading state
|
||||
useEffect(() => {
|
||||
if (state.items && state.items.length) setIsLoaded(true)
|
||||
else if (state.query.length <= minQueryLength) setIsLoaded(false)
|
||||
}, [state.items, state.query, isLoaded, minQueryLength, setIsLoaded])
|
||||
if (state.items && state.items.length) setIsLoading(false)
|
||||
else if (state.query.length <= minQueryLength) setIsLoading(true)
|
||||
}, [state.items, state.query, minQueryLength, setIsLoading])
|
||||
|
||||
// When the highlighted item changes, make sure the typeahead matches and format
|
||||
// the query text to match the case of the typeahead text
|
||||
@@ -267,7 +256,7 @@ export default function Container(props) {
|
||||
|
||||
{isDropdown() && (
|
||||
<Items
|
||||
isLoading={!isLoaded}
|
||||
isLoading={isLoading}
|
||||
items={state.items}
|
||||
loadingMessage={loadingMessage}
|
||||
noItemsMessage={noItemsMessage}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useContext } from 'react'
|
||||
import setify from 'setify' // Sets input value without changing cursor position
|
||||
//import startsWithCaseInsensitive from '../../utils/startsWithCaseInsensitive'
|
||||
import { StateContext } from '../../context/state'
|
||||
import { setItems } from '../../actions/actions'
|
||||
|
||||
export const useItemsState = (swrData) => {
|
||||
const { dispatch } = useContext(StateContext)
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setItems(swrData || []))
|
||||
}, [swrData])
|
||||
}
|
||||
|
||||
export const useAutoFocus = (queryInput, autoFocus) => { // TODO: might be able to use autofocus property of input for this - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-autofocus
|
||||
useEffect(() => {
|
||||
|
||||
@@ -78,8 +78,6 @@ const swrOptions = (isImmutable) => {
|
||||
}
|
||||
|
||||
const useData = (query, isImmutable, itemGroups, defaultItemGroups, minQueryLength, maxItems) => {
|
||||
console.log({query})
|
||||
|
||||
// See: https://github.com/vercel/swr/discussions/1810
|
||||
const dummyArgToEnsureCachingOfZeroLengthStrings = 'X'
|
||||
|
||||
|
||||
@@ -6,14 +6,17 @@ import undef from '../utils/undef'
|
||||
const StateContext = createContext() //TODO: Rename GlobalStateContext
|
||||
|
||||
const StateContextProvider = (props) => {
|
||||
const { children, splitChar, styles = {}, text = '', items = [] } = props
|
||||
const { splitChar, styles = {}, text = '', items = [] } = props
|
||||
const { children, ...propsMinusChildren} = props
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
query: text,
|
||||
items,
|
||||
highlighted: undef,
|
||||
selected: undef,
|
||||
isLoading: false,
|
||||
customStyles: styles,
|
||||
splitChar
|
||||
splitChar,
|
||||
props: propsMinusChildren
|
||||
})
|
||||
|
||||
useEffect(() => dispatch(setQuery(text)), [text]) // TODO: Is this needed?
|
||||
|
||||
@@ -6,14 +6,24 @@ import { StateContextProvider } from './context/state'
|
||||
import isUndefined from './utils/isUndefined'
|
||||
import Container from './components/container'
|
||||
|
||||
// Set prop defaults before passing them on to components
|
||||
const propDefaults = {
|
||||
autoFocus: false,
|
||||
debounceWait: 250,
|
||||
defaultItemGroupsAreImmutable: true,
|
||||
isDisabled: false,
|
||||
itemGroupsAreImmutable: true,
|
||||
maxItems: 10,
|
||||
minQueryLength: 1,
|
||||
placeholder: ''
|
||||
}
|
||||
|
||||
export default function Turnstone(props) {
|
||||
const { splitChar, styles, text } = props
|
||||
const newProps = {...propDefaults, ...props}
|
||||
|
||||
return (
|
||||
<StateContextProvider
|
||||
splitChar={splitChar}
|
||||
styles={styles}
|
||||
text={text}>
|
||||
<Container {...props} />
|
||||
<StateContextProvider {...newProps}>
|
||||
<Container {...newProps} />
|
||||
</StateContextProvider>
|
||||
)
|
||||
}
|
||||
@@ -63,7 +73,15 @@ Turnstone.propTypes = {
|
||||
},
|
||||
itemGroupsAreImmutable: PropTypes.bool,
|
||||
loadingMessage: PropTypes.string,
|
||||
minQueryLength: PropTypes.number,
|
||||
minQueryLength: (props) => {
|
||||
PropTypes.checkPropTypes(
|
||||
{minQueryLength: PropTypes.number},
|
||||
{minQueryLength: props.minQueryLength},
|
||||
'prop', 'Turnstone'
|
||||
)
|
||||
if(props.minQueryLength < propDefaults.minQueryLength)
|
||||
return new Error(`Prop "minQueryLength" must be a number greater than ${propDefaults.minQueryLength - 1}`)
|
||||
},
|
||||
noItemsMessage: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onSelect: PropTypes.func,
|
||||
|
||||
@@ -23,8 +23,16 @@ describe('Turnstone', () => {
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('Turnstone component passes all props to Container component', () => {
|
||||
test('Turnstone component passes all props to Container component along with default props', () => {
|
||||
expect(component.root.children[0].children[0].props).toEqual({
|
||||
autoFocus: false,
|
||||
debounceWait: 250,
|
||||
defaultItemGroupsAreImmutable: true,
|
||||
isDisabled: false,
|
||||
itemGroupsAreImmutable: true,
|
||||
maxItems: 10,
|
||||
minQueryLength: 1,
|
||||
placeholder: '',
|
||||
data,
|
||||
dataSearchType
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user