Skip to content

Commit 5f077a9

Browse files
authored
Biz/dj 2800 add async options pagination in connect react (#15248)
* Add load more button for async options * Hide load more button on error * Bump package version * Fix linter errors * Hook up load more button * Disable eslint error * Fix canLoadMore logic * Remove React import statement * Remove React import statement * Remove React import statement * Update pnpm-lock.yaml
1 parent 34f5a2a commit 5f077a9

File tree

12 files changed

+167
-29
lines changed

12 files changed

+167
-29
lines changed

components/bluesnap/bluesnap.app.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ export default {
88
console.log(Object.keys(this.$auth));
99
},
1010
},
11-
};
11+
};

components/grafana/grafana.app.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ export default {
88
console.log(Object.keys(this.$auth));
99
},
1010
},
11-
};
11+
};

components/polymer_co/polymer_co.app.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ export default {
88
console.log(Object.keys(this.$auth));
99
},
1010
},
11-
};
11+
};

components/shutterstock/shutterstock.app.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ export default {
88
console.log(Object.keys(this.$auth));
99
},
1010
},
11-
};
11+
};

packages/connect-react/examples/nextjs/package-lock.json

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/connect-react/examples/nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
"dependencies": {
99
"@pipedream/connect-react": "file:../..",
10-
"@pipedream/sdk": "^1.0.6",
10+
"@pipedream/sdk": "^1.1.4",
1111
"next": "15.0.3",
1212
"react": "19.0.0-rc-66855b96-20241106",
1313
"react-dom": "19.0.0-rc-66855b96-20241106"

packages/connect-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/connect-react",
3-
"version": "1.0.0-preview.18",
3+
"version": "1.0.0-preview.19",
44
"description": "Pipedream Connect library for React",
55
"files": [
66
"dist"

packages/connect-react/src/components/ControlSelect.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
import { useMemo } from "react";
2-
import Select, { Props as ReactSelectProps } from "react-select";
2+
import Select, {
3+
Props as ReactSelectProps, components,
4+
} from "react-select";
35
import type { CSSObjectWithLabel } from "react-select";
46
import CreatableSelect from "react-select/creatable";
57
import { useFormFieldContext } from "../hooks/form-field-context";
68
import { useCustomize } from "../hooks/customization-context";
79
import type { BaseReactSelectProps } from "../hooks/customization-context";
10+
import { LoadMoreButton } from "./LoadMoreButton";
811

912
// XXX T and ConfigurableProp should be related
1013
type ControlSelectProps<T> = {
1114
isCreatable?: boolean;
1215
options: {label: string; value: T;}[];
1316
selectProps?: ReactSelectProps;
17+
showLoadMoreButton?: boolean;
18+
onLoadMore?: () => void;
1419
};
1520

1621
export function ControlSelect<T>({
17-
isCreatable, options, selectProps,
22+
isCreatable, options, selectProps, showLoadMoreButton, onLoadMore,
1823
}: ControlSelectProps<T>) {
1924
const formFieldCtx = useFormFieldContext();
2025
const {
@@ -24,7 +29,7 @@ export function ControlSelect<T>({
2429
select, theme,
2530
} = useCustomize();
2631

27-
const baseSelectProps: BaseReactSelectProps<any, any, any> = {
32+
const baseSelectProps: BaseReactSelectProps<never, never, never> = {
2833
styles: {
2934
container: (base): CSSObjectWithLabel => ({
3035
...base,
@@ -69,6 +74,28 @@ export function ControlSelect<T>({
6974
options,
7075
]);
7176

77+
const LoadMore = ({
78+
// eslint-disable-next-line react/prop-types
79+
children, ...props
80+
}) => {
81+
return (
82+
<components.MenuList {...props}>
83+
{ children }
84+
<div className="pt-4">
85+
<LoadMoreButton onChange={onLoadMore}/>
86+
</div>
87+
</components.MenuList>
88+
)
89+
}
90+
91+
const props = select.getProps("controlSelect", baseSelectProps)
92+
if (showLoadMoreButton) {
93+
props.components = {
94+
// eslint-disable-next-line react/prop-types
95+
...props.components,
96+
MenuList: LoadMore,
97+
}
98+
}
7299
const MaybeCreatableSelect = isCreatable
73100
? CreatableSelect
74101
: Select;
@@ -81,7 +108,7 @@ export function ControlSelect<T>({
81108
isMulti={prop.type.endsWith("[]")}
82109
isClearable={true}
83110
required={!prop.optional}
84-
{...select.getProps("controlSelect", baseSelectProps)}
111+
{...props}
85112
{...selectProps}
86113
onChange={(o) => {
87114
if (o) {
@@ -101,7 +128,9 @@ export function ControlSelect<T>({
101128
}
102129
} else if (typeof o === "object" && "value" in o) {
103130
if (prop.withLabel) {
104-
onChange({__lv: o});
131+
onChange({
132+
__lv: o,
133+
});
105134
} else {
106135
onChange(o.value);
107136
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useCustomize } from "../hooks/customization-context";
2+
import type { CSSProperties } from "react";
3+
4+
export type ButtonProps = {
5+
onChange: () => void;
6+
};
7+
8+
export const LoadMoreButton = (props: ButtonProps) => {
9+
const { onChange } = props;
10+
const {
11+
getProps, theme,
12+
} = useCustomize();
13+
14+
const baseStyles: CSSProperties = {
15+
backgroundColor: theme.colors.primary,
16+
borderRadius: theme.borderRadius,
17+
border: "solid 1px",
18+
borderColor: theme.colors.primary25,
19+
color: theme.colors.primary25,
20+
padding: "0.5rem",
21+
fontSize: "0.8125rem",
22+
fontWeight: "450",
23+
gridArea: "control",
24+
cursor: "pointer",
25+
width: "100%",
26+
};
27+
28+
return (
29+
<button onClick={onChange} type="button" {...getProps("loadMoreButton", baseStyles, props)}>
30+
Load More
31+
</button>
32+
);
33+
};

packages/connect-react/src/components/RemoteOptionsContainer.tsx

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,40 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
2929
setQuery,
3030
] = useState("");
3131

32+
const [
33+
page,
34+
setPage,
35+
] = useState<number>(0);
36+
37+
const [
38+
canLoadMore,
39+
setCanLoadMore,
40+
] = useState<boolean>(true);
41+
42+
const [
43+
context,
44+
setContext,
45+
] = useState<never | undefined>(undefined);
46+
47+
const [
48+
pageable,
49+
setPageable,
50+
] = useState({
51+
page: 0,
52+
prevContext: {},
53+
data: [],
54+
values: new Set(),
55+
})
56+
3257
const configuredPropsUpTo: Record<string, unknown> = {};
3358
for (let i = 0; i < idx; i++) {
3459
const prop = configurableProps[i];
3560
configuredPropsUpTo[prop.name] = configuredProps[prop.name];
3661
}
3762
const componentConfigureInput: ComponentConfigureOpts = {
3863
userId,
64+
page,
65+
prevContext: context,
3966
componentId: component.key,
4067
propName: prop.name,
4168
configuredProps: configuredPropsUpTo,
@@ -55,9 +82,18 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
5582
setError,
5683
] = useState<{ name: string; message: string; }>();
5784

85+
const onLoadMore = () => {
86+
setPage(pageable.page)
87+
setContext(pageable.prevContext)
88+
setPageable({
89+
...pageable,
90+
prevContext: {},
91+
})
92+
}
93+
5894
// TODO handle error!
5995
const {
60-
isFetching, data, refetch,
96+
isFetching, refetch,
6197
} = useQuery({
6298
queryKey: [
6399
"componentConfigure",
@@ -67,11 +103,11 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
67103
setError(undefined);
68104
const res = await client.componentConfigure(componentConfigureInput);
69105

70-
// console.log("res", res)
71106
// XXX look at errors in response here too
72107
const {
73108
options, stringOptions, errors,
74109
} = res;
110+
75111
if (errors?.length) {
76112
// TODO field context setError? (for validity, etc.)
77113
try {
@@ -84,8 +120,9 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
84120
}
85121
return [];
86122
}
123+
let _options = []
87124
if (options?.length) {
88-
return options;
125+
_options = options;
89126
}
90127
if (stringOptions?.length) {
91128
const options = [];
@@ -95,13 +132,45 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
95132
value: stringOption,
96133
});
97134
}
98-
return options;
135+
_options = options;
136+
}
137+
138+
const newOptions = []
139+
const allValues = new Set(pageable.values)
140+
for (const o of _options || []) {
141+
const value = typeof o === "string"
142+
? o
143+
: o.value
144+
if (allValues.has(value)) {
145+
continue
146+
}
147+
allValues.add(value)
148+
newOptions.push(o)
149+
}
150+
let data = pageable.data
151+
if (newOptions.length) {
152+
data = [
153+
...pageable.data,
154+
...newOptions,
155+
]
156+
setPageable({
157+
page: page + 1,
158+
prevContext: res.context,
159+
data,
160+
values: allValues,
161+
})
162+
} else {
163+
setCanLoadMore(false)
99164
}
100-
return [];
165+
return data;
101166
},
102167
enabled: !!queryEnabled,
103168
});
104169

170+
const showLoadMoreButton = () => {
171+
return !isFetching && !error && canLoadMore
172+
}
173+
105174
// TODO show error in different spot!
106175
const placeholder = error
107176
? error.message
@@ -116,7 +185,9 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
116185

117186
return (
118187
<ControlSelect
119-
options={data || []}
188+
showLoadMoreButton={showLoadMoreButton()}
189+
onLoadMore={onLoadMore}
190+
options={pageable.data}
120191
// XXX isSearchable if pageQuery? or maybe in all cases? or maybe NOT when pageQuery
121192
selectProps={{
122193
isLoading: isFetching,

0 commit comments

Comments
 (0)