Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/connect-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Changelog

# [1.2.1] - 2025-06-07

- Fixing the SelectApp component to properly handle controlled values when not found in search results
- Fixing infinite re-render issue in ComponentFormContainer by memoizing configurableProps

# [1.2.0] - 2025-06-05

- Adding basic support for 'sql' prop types
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": "1.2.0",
"version": "1.2.1",
"description": "Pipedream Connect library for React",
"files": [
"dist"
Expand Down
34 changes: 29 additions & 5 deletions packages/connect-react/src/components/SelectApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
useId, useState,
useId, useState, useEffect,
} from "react";
import Select, { components } from "react-select";
import { useApps } from "../hooks/use-apps";
Expand All @@ -13,11 +13,28 @@ type SelectAppProps = {
export function SelectApp({
value, onChange,
}: SelectAppProps) {
const [
inputValue,
setInputValue,
] = useState("");
const [
q,
setQ,
] = useState(""); // XXX can we just use Select ref.value instead?
] = useState(""); // Debounced query value

const instanceId = useId();

// Debounce the search query
useEffect(() => {
const timer = setTimeout(() => {
setQ(inputValue);
}, 300); // 300ms delay

return () => clearTimeout(timer);
}, [
inputValue,
]);

const {
isLoading,
// TODO error
Expand All @@ -29,7 +46,11 @@ export function SelectApp({
Option,
SingleValue,
} = components;
const selectedValue = apps?.find((o) => o.name_slug === value?.name_slug) || null;
// If we have a value prop but it's not in the search results, use the value prop directly
const selectedValue = apps?.find((o) => o.name_slug === value?.name_slug)
|| (value?.name_slug
? value as AppResponse
: null);
return (
<Select
instanceId={instanceId}
Expand Down Expand Up @@ -86,8 +107,11 @@ export function SelectApp({
getOptionValue={(o) => o.name_slug}
value={selectedValue}
onChange={(o) => onChange?.((o as AppResponse) || undefined)}
onInputChange={(v) => {
if (v) setQ(v)
onInputChange={(v, { action }) => {
// Only update on user input, not on blur/menu-close/etc
if (action === "input-change") {
setInputValue(v)
}
}}
isLoading={isLoading}
/>
Expand Down
38 changes: 24 additions & 14 deletions packages/connect-react/src/hooks/form-context.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
createContext, useContext, useEffect, useId, useState, type ReactNode,
createContext, useContext, useEffect, useId, useMemo, useState, type ReactNode,
} from "react";
import isEqual from "lodash.isequal";
import { useQuery } from "@tanstack/react-query";
Expand Down Expand Up @@ -101,7 +101,7 @@ export const FormContextProvider = <T extends ConfigurableProps>({
const [
sdkErrors,
setSdkErrors,
] = useState<SdkError[]>([])
] = useState<SdkError[]>([]);

const [
enabledOptionalProps,
Expand Down Expand Up @@ -187,20 +187,30 @@ export const FormContextProvider = <T extends ConfigurableProps>({
]);

// XXX fix types of dynamicProps, props.component so this type decl not needed
let configurableProps: T = dynamicProps?.configurableProps || formProps.component.configurable_props || [];
if (propNames?.length) {
const _configurableProps = [];
for (const prop of configurableProps) {
// TODO decided propNames (and hideOptionalProps) should NOT filter dynamic props
if (propNames.findIndex((name) => prop.name === name) >= 0) {
_configurableProps.push(prop);
const configurableProps = useMemo<T>(() => {
let props: T = dynamicProps?.configurableProps || formProps.component.configurable_props || [];
if (propNames?.length) {
const _configurableProps = [];
for (const prop of props) {
// TODO decided propNames (and hideOptionalProps) should NOT filter dynamic props
if (propNames.findIndex((name) => prop.name === name) >= 0) {
_configurableProps.push(prop);
}
}
props = _configurableProps as unknown as T; // XXX
}
configurableProps = _configurableProps as unknown as T; // XXX
}
if (reloadPropIdx != null) {
configurableProps = configurableProps.slice(0, reloadPropIdx + 1) as unknown as T; // XXX
}
if (reloadPropIdx != null) {
props = Array.isArray(props)
? props.slice(0, reloadPropIdx + 1) as unknown as T // eslint-disable-line react/prop-types
: props; // XXX
}
return props;
}, [
dynamicProps?.configurableProps,
formProps.component.configurable_props,
propNames,
reloadPropIdx,
]);

// these validations are necessary because they might override PropInput for number case for instance
// so can't rely on that base control form validation
Expand Down
Loading