Skip to content
Draft
15 changes: 15 additions & 0 deletions packages/connect-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

# Changelog

## [2.1.0] - 2025-10-10

### Added

- Infinite scroll pagination for `SelectApp` and `SelectComponent` dropdowns
- `useApps` and `useComponents` hooks now support pagination with automatic accumulation of results
- Default page size increased to 50 items per request for better UX

### Fixed

- Scroll position preservation in `SelectApp` and `SelectComponent` during infinite scroll
- Scroll position preservation in `ControlSelect` when clicking "Load More" button for remote options
- Prevent component remounting that caused scroll jumps during pagination
- Remote options now properly reset when parent props change (e.g., switching accounts)

## [2.0.0] - 2025-10-02

### Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/connect-react",
"version": "2.0.0",
"version": "2.1.0",
"description": "Pipedream Connect library for React",
"files": [
"dist"
Expand Down
69 changes: 48 additions & 21 deletions packages/connect-react/src/components/ControlSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
useEffect,
useMemo,
useState,
useRef,
} from "react";
import type {
CSSObjectWithLabel, MenuListProps,
Expand Down Expand Up @@ -112,30 +113,55 @@ export function ControlSelect<T extends PropOptionValue>({
selectOptions,
]);

const LoadMore = ({
// eslint-disable-next-line react/prop-types
children, ...props
}: MenuListProps<LabelValueOption<T>, boolean>) => {
return (
<components.MenuList {...props}>
{children}
<div className="pt-4">
<LoadMoreButton onChange={onLoadMore || (() => { })} />
</div>
</components.MenuList>
)
}

const props = select.getProps("controlSelect", baseSelectProps)

const finalComponents = {
...props.components,
...componentsOverride,
};
// Use ref to store latest onLoadMore callback
// This allows stable component reference while calling current callback
const onLoadMoreRef = useRef(onLoadMore);
useEffect(() => {
onLoadMoreRef.current = onLoadMore;
}, [
onLoadMore,
]);

if (showLoadMoreButton) {
finalComponents.MenuList = LoadMore;
}
// Use ref to store latest showLoadMoreButton value
const showLoadMoreButtonRef = useRef(showLoadMoreButton);
useEffect(() => {
showLoadMoreButtonRef.current = showLoadMoreButton;
}, [
showLoadMoreButton,
]);

// Memoize custom components to prevent remounting
// Recompute when caller/customizer supplies new component overrides
const finalComponents = useMemo(() => {
const base = {
...props.components,
...componentsOverride,
};

// Always set MenuList, conditionally render button inside
const CustomMenuList = ({
// eslint-disable-next-line react/prop-types
children, ...menuProps
}: MenuListProps<LabelValueOption<T>, boolean>) => (
<components.MenuList {...menuProps}>
{children}
{showLoadMoreButtonRef.current && (
<div className="pt-4">
<LoadMoreButton onChange={() => onLoadMoreRef.current?.()} />
</div>
)}
</components.MenuList>
);
CustomMenuList.displayName = "CustomMenuList";
base.MenuList = CustomMenuList;

return base;
}, [
props.components,
componentsOverride,
]);

const handleCreate = (inputValue: string) => {
const newOption = sanitizeOption(inputValue as T)
Expand Down Expand Up @@ -215,6 +241,7 @@ export function ControlSelect<T extends PropOptionValue>({
onChange={handleChange}
{...props}
{...selectProps}
components={finalComponents}
{...additionalProps}
/>
);
Expand Down
Loading
Loading