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
6 changes: 6 additions & 0 deletions packages/connect-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<!-- markdownlint-disable MD024 -->
# Changelog

# [1.0.0-preview.24] - 2025-01-24

- Fix the bug where inputting multiple strings into an array prop would merge the strings into one
- Fix custom string input for remote options
- Fix the reloading of a previously selected remote option when re-rendering the form component

# [1.0.0-preview.23] - 2025-01-22

- Show the prop label instead of the value after selecting from a dropdown for string array props
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-react/examples/nextjs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.0.0-preview.23",
"version": "1.0.0-preview.24",
"description": "Pipedream Connect library for React",
"files": [
"dist"
Expand Down
154 changes: 109 additions & 45 deletions packages/connect-react/src/components/ControlSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useMemo } from "react";
import {
useEffect,
useMemo, useState,
} from "react";
import Select, {
Props as ReactSelectProps, components,
} from "react-select";
Expand Down Expand Up @@ -28,6 +31,26 @@ export function ControlSelect<T>({
const {
select, theme,
} = useCustomize();
const [
selectOptions,
setSelectOptions,
] = useState(options);
const [
rawValue,
setRawValue,
] = useState(value);

useEffect(() => {
setSelectOptions(options)
}, [
options,
])

useEffect(() => {
setRawValue(value)
}, [
value,
])

const baseSelectProps: BaseReactSelectProps<never, never, never> = {
styles: {
Expand All @@ -40,7 +63,7 @@ export function ControlSelect<T>({
};

const selectValue = useMemo(() => {
let ret = value;
let ret = rawValue;
if (ret != null) {
if (Array.isArray(ret)) {
// if simple, make lv (XXX combine this with other place this happens)
Expand All @@ -51,7 +74,7 @@ export function ControlSelect<T>({
label: o,
value: o,
}
for (const item of options) {
for (const item of selectOptions) {
if (item.value === o) {
obj = item;
break;
Expand All @@ -62,23 +85,28 @@ export function ControlSelect<T>({
ret = lvs;
}
} else if (typeof ret !== "object") {
const lvOptions = options?.[0] && typeof options[0] === "object";
const lvOptions = selectOptions?.[0] && typeof selectOptions[0] === "object";
if (lvOptions) {
for (const item of options) {
if (item.value === value) {
for (const item of selectOptions) {
if (item.value === rawValue) {
ret = item;
break;
}
}
} else {
ret = {
label: rawValue,
value: rawValue,
}
}
} else if (ret.__lv) {
ret = ret.__lv
}
}
return ret;
}, [
value,
options,
rawValue,
selectOptions,
]);

const LoadMore = ({
Expand All @@ -105,58 +133,94 @@ export function ControlSelect<T>({
}

const handleCreate = (inputValue: string) => {
options.unshift({
label: inputValue,
value: inputValue,
})
const createOption = (input: unknown) => {
if (typeof input === "object") return input
return {
label: input,
value: input,
}
}
const newOption = createOption(inputValue)
let newRawValue = newOption
const newSelectOptions = selectOptions
? [
newOption,
...selectOptions,
]
: [
newOption,
]
setSelectOptions(newSelectOptions);
if (prop.type.endsWith("[]")) {
if (Array.isArray(rawValue)) {
newRawValue = [
...rawValue.map(createOption),
newOption,
]
} else {
newRawValue = [
newOption,
]
}
}
setRawValue(newRawValue)
handleChange(newRawValue)
};

const handleChange = (o: unknown) => {
if (o) {
if (Array.isArray(o)) {
if (typeof o[0] === "object" && "value" in o[0]) {
const vs = [];
for (const _o of o) {
if (prop.withLabel) {
vs.push(_o);
} else {
vs.push(_o.value);
}
}
onChange(vs);
} else {
onChange(o);
}
} else if (typeof o === "object" && "value" in o) {
if (prop.withLabel) {
onChange({
__lv: o,
});
} else {
onChange(o.value);
}
} else {
throw new Error("unhandled option type"); // TODO
}
} else {
onChange(undefined);
}
}
Comment on lines +170 to +200
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and type checking.

The error handling and type checking in handleChange could be improved:

  1. The error message is not descriptive enough
  2. The type checking logic could be simplified using TypeScript type guards
+ type SelectOption = { value: unknown; label: string };
+ 
+ function isSelectOption(o: unknown): o is SelectOption {
+   return typeof o === "object" && o !== null && "value" in o;
+ }
+ 
  const handleChange = (o: unknown) => {
    if (o) {
      if (Array.isArray(o)) {
-        if (typeof o[0] === "object" && "value" in o[0]) {
+        if (o.length > 0 && isSelectOption(o[0])) {
          const vs = [];
          for (const _o of o) {
            if (prop.withLabel) {
              vs.push(_o);
            } else {
              vs.push(_o.value);
            }
          }
          onChange(vs);
        } else {
          onChange(o);
        }
-      } else if (typeof o === "object" && "value" in o) {
+      } else if (isSelectOption(o)) {
        if (prop.withLabel) {
          onChange({
            __lv: o,
          });
        } else {
          onChange(o.value);
        }
      } else {
-        throw new Error("unhandled option type"); // TODO
+        throw new Error(`Unhandled option type: ${typeof o}. Expected an array or an object with 'value' property.`);
      }
    } else {
      onChange(undefined);
    }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleChange = (o: unknown) => {
if (o) {
if (Array.isArray(o)) {
if (typeof o[0] === "object" && "value" in o[0]) {
const vs = [];
for (const _o of o) {
if (prop.withLabel) {
vs.push(_o);
} else {
vs.push(_o.value);
}
}
onChange(vs);
} else {
onChange(o);
}
} else if (typeof o === "object" && "value" in o) {
if (prop.withLabel) {
onChange({
__lv: o,
});
} else {
onChange(o.value);
}
} else {
throw new Error("unhandled option type"); // TODO
}
} else {
onChange(undefined);
}
}
type SelectOption = { value: unknown; label: string };
function isSelectOption(o: unknown): o is SelectOption {
return typeof o === "object" && o !== null && "value" in o;
}
const handleChange = (o: unknown) => {
if (o) {
if (Array.isArray(o)) {
if (o.length > 0 && isSelectOption(o[0])) {
const vs = [];
for (const _o of o) {
if (prop.withLabel) {
vs.push(_o);
} else {
vs.push(_o.value);
}
}
onChange(vs);
} else {
onChange(o);
}
} else if (isSelectOption(o)) {
if (prop.withLabel) {
onChange({
__lv: o,
});
} else {
onChange(o.value);
}
} else {
throw new Error(`Unhandled option type: ${typeof o}. Expected an array or an object with 'value' property.`);
}
} else {
onChange(undefined);
}
}


const additionalProps = {
onCreateOption: prop.remoteOptions
? handleCreate
: undefined,
}

const MaybeCreatableSelect = isCreatable
? CreatableSelect
: Select;
return (
<MaybeCreatableSelect
inputId={id}
instanceId={id}
options={options}
options={selectOptions}
value={selectValue}
isMulti={prop.type.endsWith("[]")}
isClearable={true}
required={!prop.optional}
{...props}
{...selectProps}
onCreateOption={handleCreate}
onChange={(o) => {
if (o) {
if (Array.isArray(o)) {
if (typeof o[0] === "object" && "value" in o[0]) {
const vs = [];
for (const _o of o) {
if (prop.withLabel) {
vs.push(_o);
} else {
vs.push(_o.value);
}
}
onChange(vs);
} else {
onChange(o);
}
} else if (typeof o === "object" && "value" in o) {
if (prop.withLabel) {
onChange({
__lv: o,
});
} else {
onChange(o.value);
}
} else {
throw new Error("unhandled option type"); // TODO
}
} else {
onChange(undefined);
}
}}
{...additionalProps}
onChange={handleChange}
/>
);
}
3 changes: 1 addition & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading