Add dynamic loading
This commit is contained in:
parent
0bf7594ce0
commit
d99321be84
6 changed files with 156 additions and 55 deletions
|
@ -6,10 +6,14 @@ import { maybe } from "@saleor/misc";
|
||||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
import Decorator from "@saleor/storybook/Decorator";
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
import { ChoiceProvider } from "@saleor/storybook/mock";
|
import { ChoiceProvider } from "@saleor/storybook/mock";
|
||||||
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import { countries } from "./fixtures";
|
import { countries } from "./fixtures";
|
||||||
import SingleAutocompleteSelectField, {
|
import SingleAutocompleteSelectField, {
|
||||||
SingleAutocompleteSelectFieldProps
|
SingleAutocompleteSelectFieldProps
|
||||||
} from "./SingleAutocompleteSelectField";
|
} from "./SingleAutocompleteSelectField";
|
||||||
|
import SingleAutocompleteSelectFieldContent, {
|
||||||
|
SingleAutocompleteSelectFieldContentProps
|
||||||
|
} from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
const suggestions = countries.map(c => ({ label: c.name, value: c.code }));
|
const suggestions = countries.map(c => ({ label: c.name, value: c.code }));
|
||||||
|
|
||||||
|
@ -20,11 +24,16 @@ const props: SingleAutocompleteSelectFieldProps = {
|
||||||
loading: false,
|
loading: false,
|
||||||
name: "country",
|
name: "country",
|
||||||
onChange: () => undefined,
|
onChange: () => undefined,
|
||||||
placeholder: "Select country"
|
placeholder: "Select country",
|
||||||
|
value: suggestions[0].value
|
||||||
};
|
};
|
||||||
|
|
||||||
const Story: React.FC<
|
const Story: React.FC<
|
||||||
Partial<SingleAutocompleteSelectFieldProps>
|
Partial<
|
||||||
|
SingleAutocompleteSelectFieldProps & {
|
||||||
|
enableLoadMore: boolean;
|
||||||
|
}
|
||||||
|
>
|
||||||
> = storyProps => {
|
> = storyProps => {
|
||||||
const [displayValue, setDisplayValue] = React.useState(suggestions[0].label);
|
const [displayValue, setDisplayValue] = React.useState(suggestions[0].label);
|
||||||
|
|
||||||
|
@ -32,14 +41,12 @@ const Story: React.FC<
|
||||||
<Form initial={{ country: suggestions[0].value }}>
|
<Form initial={{ country: suggestions[0].value }}>
|
||||||
{({ change, data }) => (
|
{({ change, data }) => (
|
||||||
<ChoiceProvider choices={suggestions}>
|
<ChoiceProvider choices={suggestions}>
|
||||||
{({ choices, loading, fetchChoices }) => {
|
{({ choices, fetchChoices, fetchMore, hasMore, loading }) => {
|
||||||
const handleSelect = (event: React.ChangeEvent<any>) => {
|
const handleSelect = createSingleAutocompleteSelectHandler(
|
||||||
const value: string = event.target.value;
|
change,
|
||||||
const match = choices.find(choice => choice.value === value);
|
setDisplayValue,
|
||||||
const label = maybe(() => match.label, value);
|
choices
|
||||||
setDisplayValue(label);
|
);
|
||||||
change(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
|
@ -48,9 +55,11 @@ const Story: React.FC<
|
||||||
choices={choices}
|
choices={choices}
|
||||||
fetchChoices={fetchChoices}
|
fetchChoices={fetchChoices}
|
||||||
helperText={`Value: ${data.country}`}
|
helperText={`Value: ${data.country}`}
|
||||||
|
loading={loading}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
value={data.country}
|
value={data.country}
|
||||||
loading={loading}
|
hasMore={storyProps.enableLoadMore ? hasMore : false}
|
||||||
|
onFetchMore={storyProps.enableLoadMore ? fetchMore : undefined}
|
||||||
{...storyProps}
|
{...storyProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -61,9 +70,35 @@ const Story: React.FC<
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Generics / SingleAutocompleteSelectField", module)
|
const contentProps: SingleAutocompleteSelectFieldContentProps = {
|
||||||
|
choices: suggestions.slice(0, 10),
|
||||||
|
displayCustomValue: false,
|
||||||
|
emptyOption: false,
|
||||||
|
getItemProps: () => undefined,
|
||||||
|
hasMore: false,
|
||||||
|
highlightedIndex: 0,
|
||||||
|
inputValue: suggestions[0].label,
|
||||||
|
isCustomValueSelected: false,
|
||||||
|
loading: false,
|
||||||
|
onFetchMore: () => undefined,
|
||||||
|
selectedItem: suggestions[0].value
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Select with autocomplete", module)
|
||||||
.addDecorator(CardDecorator)
|
.addDecorator(CardDecorator)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("with loaded data", () => <Story />)
|
.add("default", () => (
|
||||||
.add("with loading data", () => <Story loading={true} />)
|
<SingleAutocompleteSelectFieldContent {...contentProps} />
|
||||||
.add("with custom option", () => <Story allowCustomValues={true} />);
|
))
|
||||||
|
.add("can load more", () => (
|
||||||
|
<SingleAutocompleteSelectFieldContent {...contentProps} hasMore={true} />
|
||||||
|
))
|
||||||
|
.add("no data", () => (
|
||||||
|
<SingleAutocompleteSelectFieldContent {...contentProps} choices={[]} />
|
||||||
|
))
|
||||||
|
.add("interactive", () => <Story />)
|
||||||
|
.add("interactive with custom option", () => (
|
||||||
|
<Story allowCustomValues={true} />
|
||||||
|
))
|
||||||
|
.add("interactive with empty option", () => <Story emptyOption={true} />)
|
||||||
|
.add("interactive with load more", () => <Story enableLoadMore={true} />);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Omit } from "@material-ui/core";
|
import { Omit } from "@material-ui/core";
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
|
||||||
import { InputProps } from "@material-ui/core/Input";
|
import { InputProps } from "@material-ui/core/Input";
|
||||||
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
@ -11,6 +10,7 @@ import SingleAutocompleteSelectFieldContent, {
|
||||||
} from "./SingleAutocompleteSelectFieldContent";
|
} from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
||||||
import Debounce, { DebounceProps } from "../Debounce";
|
import Debounce, { DebounceProps } from "../Debounce";
|
||||||
|
|
||||||
|
@ -21,15 +21,15 @@ const styles = createStyles({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface SingleAutocompleteSelectFieldProps {
|
export interface SingleAutocompleteSelectFieldProps
|
||||||
|
extends Partial<FetchMoreProps> {
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
displayValue: string;
|
displayValue: string;
|
||||||
emptyOption?: boolean;
|
emptyOption?: boolean;
|
||||||
choices: SingleAutocompleteChoiceType[];
|
choices: SingleAutocompleteChoiceType[];
|
||||||
value?: string;
|
value: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
loading?: boolean;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
allowCustomValues?: boolean;
|
allowCustomValues?: boolean;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
|
@ -61,6 +61,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
displayValue,
|
displayValue,
|
||||||
emptyOption,
|
emptyOption,
|
||||||
error,
|
error,
|
||||||
|
hasMore,
|
||||||
helperText,
|
helperText,
|
||||||
label,
|
label,
|
||||||
loading,
|
loading,
|
||||||
|
@ -70,6 +71,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
InputProps,
|
InputProps,
|
||||||
fetchChoices,
|
fetchChoices,
|
||||||
onChange,
|
onChange,
|
||||||
|
onFetchMore,
|
||||||
...props
|
...props
|
||||||
}: SingleAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
}: SingleAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
||||||
const [prevDisplayValue] = useStateFromProps(displayValue);
|
const [prevDisplayValue] = useStateFromProps(displayValue);
|
||||||
|
@ -132,11 +134,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
}),
|
}),
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<div>
|
<div>
|
||||||
{loading ? (
|
<ArrowDropdownIcon onClick={toggleMenu} />
|
||||||
<CircularProgress size={20} />
|
|
||||||
) : (
|
|
||||||
<ArrowDropdownIcon onClick={toggleMenu} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
error,
|
error,
|
||||||
|
@ -156,10 +154,13 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
displayCustomValue={displayCustomValue}
|
displayCustomValue={displayCustomValue}
|
||||||
emptyOption={emptyOption}
|
emptyOption={emptyOption}
|
||||||
getItemProps={getItemProps}
|
getItemProps={getItemProps}
|
||||||
|
hasMore={hasMore}
|
||||||
highlightedIndex={highlightedIndex}
|
highlightedIndex={highlightedIndex}
|
||||||
|
loading={loading}
|
||||||
inputValue={inputValue}
|
inputValue={inputValue}
|
||||||
isCustomValueSelected={isCustomValueSelected}
|
isCustomValueSelected={isCustomValueSelected}
|
||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
|
onFetchMore={onFetchMore}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -208,4 +209,5 @@ export class SingleAutocompleteSelectField extends React.Component<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SingleAutocompleteSelectField;
|
export default SingleAutocompleteSelectField;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
import MenuItem from "@material-ui/core/MenuItem";
|
import MenuItem from "@material-ui/core/MenuItem";
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import { Theme } from "@material-ui/core/styles";
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
@ -9,16 +10,19 @@ import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import useElementScroll from "@saleor/hooks/useElementScroll";
|
import useElementScroll from "@saleor/hooks/useElementScroll";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import Hr from "../Hr";
|
import Hr from "../Hr";
|
||||||
|
|
||||||
const menuItemHeight = 46;
|
const menuItemHeight = 46;
|
||||||
const maxMenuItems = 5;
|
const maxMenuItems = 5;
|
||||||
|
const offset = 24;
|
||||||
|
|
||||||
export interface SingleAutocompleteChoiceType {
|
export interface SingleAutocompleteChoiceType {
|
||||||
label: string;
|
label: string;
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
interface SingleAutocompleteSelectFieldContentProps {
|
export interface SingleAutocompleteSelectFieldContentProps
|
||||||
|
extends Partial<FetchMoreProps> {
|
||||||
choices: SingleAutocompleteChoiceType[];
|
choices: SingleAutocompleteChoiceType[];
|
||||||
displayCustomValue: boolean;
|
displayCustomValue: boolean;
|
||||||
emptyOption: boolean;
|
emptyOption: boolean;
|
||||||
|
@ -43,6 +47,11 @@ const useStyles = makeStyles(
|
||||||
height: "auto",
|
height: "auto",
|
||||||
whiteSpace: "normal"
|
whiteSpace: "normal"
|
||||||
},
|
},
|
||||||
|
progress: {},
|
||||||
|
progressContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center"
|
||||||
|
},
|
||||||
root: {
|
root: {
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
@ -52,7 +61,9 @@ const useStyles = makeStyles(
|
||||||
zIndex: 22
|
zIndex: 22
|
||||||
},
|
},
|
||||||
shadow: {
|
shadow: {
|
||||||
boxShadow: `0px -5px 10px 0px ${theme.palette.grey[800]}`
|
"&$shadowLine": {
|
||||||
|
boxShadow: `0px -5px 10px 0px ${theme.palette.grey[800]}`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
shadowLine: {
|
shadowLine: {
|
||||||
boxShadow: `0px 0px 0px 0px ${theme.palette.grey[50]}`,
|
boxShadow: `0px 0px 0px 0px ${theme.palette.grey[50]}`,
|
||||||
|
@ -89,21 +100,38 @@ const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
displayCustomValue,
|
displayCustomValue,
|
||||||
emptyOption,
|
emptyOption,
|
||||||
getItemProps,
|
getItemProps,
|
||||||
|
hasMore,
|
||||||
highlightedIndex,
|
highlightedIndex,
|
||||||
|
loading,
|
||||||
inputValue,
|
inputValue,
|
||||||
isCustomValueSelected,
|
isCustomValueSelected,
|
||||||
selectedItem
|
selectedItem,
|
||||||
|
onFetchMore
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const anchor = React.useRef<HTMLDivElement>();
|
const anchor = React.useRef<HTMLDivElement>();
|
||||||
const scrollPosition = useElementScroll(anchor);
|
const scrollPosition = useElementScroll(anchor);
|
||||||
|
const [calledForMore, setCalledForMore] = React.useState(false);
|
||||||
|
|
||||||
const dropShadow = anchor.current
|
const scrolledToBottom = anchor.current
|
||||||
? scrollPosition.y + anchor.current.clientHeight <
|
? scrollPosition.y + anchor.current.clientHeight + offset >=
|
||||||
anchor.current.scrollHeight
|
anchor.current.scrollHeight
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!calledForMore && onFetchMore && scrolledToBottom) {
|
||||||
|
onFetchMore();
|
||||||
|
setCalledForMore(true);
|
||||||
|
}
|
||||||
|
}, [scrolledToBottom]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (calledForMore && !loading) {
|
||||||
|
setCalledForMore(false);
|
||||||
|
}
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className={classes.root} square>
|
<Paper className={classes.root} square>
|
||||||
<div className={classes.content} ref={anchor}>
|
<div className={classes.content} ref={anchor}>
|
||||||
|
@ -172,6 +200,14 @@ const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{hasMore && (
|
||||||
|
<>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<div className={classes.progressContainer}>
|
||||||
|
<CircularProgress className={classes.progress} size={24} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -185,7 +221,7 @@ const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(classes.shadowLine, {
|
className={classNames(classes.shadowLine, {
|
||||||
[classes.shadow]: dropShadow
|
[classes.shadow]: !scrolledToBottom
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export { default } from "./SingleAutocompleteSelectField";
|
export { default } from "./SingleAutocompleteSelectField";
|
||||||
export * from "./SingleAutocompleteSelectField";
|
export * from "./SingleAutocompleteSelectField";
|
||||||
|
export * from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
|
@ -7,6 +7,7 @@ const CardDecorator = storyFn => (
|
||||||
style={{
|
style={{
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
|
position: "relative",
|
||||||
width: 400
|
width: 400
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,36 +1,41 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
interface ChoiceProviderProps {
|
interface ChoiceProviderProps {
|
||||||
children: (props: {
|
children: (props: {
|
||||||
choices: Array<{
|
choices: SingleAutocompleteChoiceType[];
|
||||||
label: string;
|
hasMore: boolean;
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
fetchChoices(value: string);
|
fetchChoices: (value: string) => void;
|
||||||
|
fetchMore: () => void;
|
||||||
}) => React.ReactElement<any>;
|
}) => React.ReactElement<any>;
|
||||||
choices: Array<{
|
choices: SingleAutocompleteChoiceType[];
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
interface ChoiceProviderState {
|
interface ChoiceProviderState {
|
||||||
choices: Array<{
|
choices: SingleAutocompleteChoiceType[];
|
||||||
label: string;
|
filteredChoices: SingleAutocompleteChoiceType[];
|
||||||
value: string;
|
first: number;
|
||||||
}>;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
timeout: any;
|
timeout: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const step = 5;
|
||||||
|
|
||||||
export class ChoiceProvider extends React.Component<
|
export class ChoiceProvider extends React.Component<
|
||||||
ChoiceProviderProps,
|
ChoiceProviderProps,
|
||||||
ChoiceProviderState
|
ChoiceProviderState
|
||||||
> {
|
> {
|
||||||
state = { choices: [], loading: false, timeout: null };
|
state = {
|
||||||
|
choices: [],
|
||||||
|
filteredChoices: [],
|
||||||
|
first: step,
|
||||||
|
loading: false,
|
||||||
|
timeout: null
|
||||||
|
};
|
||||||
|
|
||||||
handleChange = (inputValue: string) => {
|
handleChange = (inputValue: string) => {
|
||||||
if (this.state.loading) {
|
if (!!this.state.timeout) {
|
||||||
clearTimeout(this.state.timeout);
|
clearTimeout(this.state.timeout);
|
||||||
}
|
}
|
||||||
const timeout = setTimeout(() => this.fetchChoices(inputValue), 500);
|
const timeout = setTimeout(() => this.fetchChoices(inputValue), 500);
|
||||||
|
@ -40,16 +45,35 @@ export class ChoiceProvider extends React.Component<
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchChoices = (inputValue: string) => {
|
handleFetchMore = () => {
|
||||||
|
if (!!this.state.timeout) {
|
||||||
|
clearTimeout(this.state.timeout);
|
||||||
|
}
|
||||||
|
const timeout = setTimeout(this.fetchMore, 500);
|
||||||
this.setState({
|
this.setState({
|
||||||
choices: this.props.choices
|
loading: true,
|
||||||
.filter(
|
timeout
|
||||||
suggestion =>
|
});
|
||||||
!inputValue ||
|
};
|
||||||
suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !==
|
|
||||||
-1
|
fetchMore = () =>
|
||||||
)
|
this.setState(prevState => ({
|
||||||
.slice(0, 10),
|
filteredChoices: prevState.choices.slice(0, prevState.first + step),
|
||||||
|
first: prevState.first + step,
|
||||||
|
loading: false,
|
||||||
|
timeout: null
|
||||||
|
}));
|
||||||
|
|
||||||
|
fetchChoices = (inputValue: string) => {
|
||||||
|
const choices = this.props.choices.filter(
|
||||||
|
suggestion =>
|
||||||
|
!inputValue ||
|
||||||
|
suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
choices,
|
||||||
|
filteredChoices: choices.slice(0, step),
|
||||||
|
first: step,
|
||||||
loading: false,
|
loading: false,
|
||||||
timeout: null
|
timeout: null
|
||||||
});
|
});
|
||||||
|
@ -57,8 +81,10 @@ export class ChoiceProvider extends React.Component<
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.props.children({
|
return this.props.children({
|
||||||
choices: this.state.choices,
|
choices: this.state.filteredChoices,
|
||||||
fetchChoices: this.handleChange,
|
fetchChoices: this.handleChange,
|
||||||
|
fetchMore: this.handleFetchMore,
|
||||||
|
hasMore: this.state.choices.length > this.state.filteredChoices.length,
|
||||||
loading: this.state.loading
|
loading: this.state.loading
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue