-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Fixes GH-17399 #17747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+252
−33
Merged
Fixes GH-17399 #17747
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
c6fba55
Fixes GH-17399
dannyroosevelt 679ab02
Update pnpm-lock.yaml
dannyroosevelt 68e599c
Update pnpm-lock.yaml
dannyroosevelt 7fa33d9
Update pnpm-lock.yaml
dannyroosevelt 25acc0c
Merge branch 'master' into danny/connect-react/gh-17399
dannyroosevelt 0dea0ed
Linting
dannyroosevelt 5e13bfb
Merge branch 'danny/connect-react/gh-17399' of github.com:PipedreamHQ…
dannyroosevelt 7c6155b
Merge branch 'danny/connect-react/gh-17399' of github.com:PipedreamHQ…
dannyroosevelt 8a02075
Linting and documenting the utility functions
dannyroosevelt b76c99a
Merge branch 'master' into danny/connect-react/gh-17399
dannyroosevelt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
/** | ||
* Represents an option object with a value and optional label. | ||
* Used by react-select and similar components. | ||
*/ | ||
export interface OptionWithValue { | ||
/** The actual value of the option (string or number) */ | ||
value: string | number; | ||
/** Optional display label for the option */ | ||
label?: string; | ||
/** Internal wrapper object (used by form handling logic) */ | ||
__lv?: unknown; | ||
} | ||
|
||
/** | ||
* Type guard to check if a value is a string. | ||
* @param value - The value to check | ||
* @returns true if the value is a string | ||
*/ | ||
export function isString(value: unknown): value is string { | ||
return typeof value === "string"; | ||
} | ||
|
||
/** | ||
* Type guard to check if a value is a valid OptionWithValue object. | ||
* Validates that the object has a 'value' property that is either a string or number. | ||
* @param value - The value to check | ||
* @returns true if the value is a valid OptionWithValue | ||
*/ | ||
export function isOptionWithValue(value: unknown): value is OptionWithValue { | ||
return ( | ||
value !== null && | ||
typeof value === "object" && | ||
!Array.isArray(value) && | ||
"value" in value && | ||
(typeof (value as Record<string, unknown>).value === "string" || typeof (value as Record<string, unknown>).value === "number") | ||
); | ||
} | ||
|
||
/** | ||
* Type guard to check if a value is an array of strings. | ||
* @param value - The value to check | ||
* @returns true if the value is a string array | ||
*/ | ||
export function isStringArray(value: unknown): value is string[] { | ||
return Array.isArray(value) && value.every((item) => typeof item === "string"); | ||
} | ||
|
||
/** | ||
* Type guard to check if a value is an array of OptionWithValue objects. | ||
* @param value - The value to check | ||
* @returns true if the value is an array of valid OptionWithValue objects | ||
*/ | ||
export function isOptionArray(value: unknown): value is OptionWithValue[] { | ||
return Array.isArray(value) && value.every((item) => isOptionWithValue(item)); | ||
} | ||
|
||
/** | ||
* Normalizes an unknown value into either a string or OptionWithValue. | ||
* Used for basic option processing where the input format is uncertain. | ||
* @param option - The option to normalize | ||
* @returns A normalized string or OptionWithValue object | ||
*/ | ||
export function normalizeOption(option: unknown): OptionWithValue | string { | ||
if (isString(option)) { | ||
return option; | ||
} | ||
if (isOptionWithValue(option)) { | ||
return option; | ||
} | ||
return String(option); | ||
} | ||
dannyroosevelt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Normalizes an array of unknown values into an array of strings or OptionWithValue objects. | ||
* Handles cases where the input might not be an array by returning an empty array. | ||
* @param options - The options array to normalize | ||
* @returns An array of normalized options | ||
*/ | ||
export function normalizeOptions(options: unknown): Array<OptionWithValue | string> { | ||
if (!Array.isArray(options)) { | ||
return []; | ||
} | ||
return options.map(normalizeOption); | ||
} | ||
|
||
/** | ||
* Sanitizes an option to ensure it has proper primitive values for label/value. | ||
* This is the main utility for processing complex nested option structures that can | ||
* come from various sources (APIs, form data, etc.) into a format compatible with react-select. | ||
* | ||
* Handles multiple nesting scenarios: | ||
* 1. String options: returned as-is (e.g., "simple-option") | ||
* 2. __lv wrapper objects: extracts inner option from {__lv: {label: "...", value: "..."}} | ||
* 3. Nested label/value objects: handles {label: {label: "Documents"}, value: {value: "123"}} | ||
* | ||
* This function was created to fix React error #31 where nested objects were being | ||
* passed to React components that expected primitive values. | ||
* | ||
* @param option - The option to sanitize (can be string, object, or complex nested structure) | ||
* @returns A clean option with primitive label/value or a string | ||
* | ||
* @example | ||
* // Simple string | ||
* sanitizeOption("hello") // returns "hello" | ||
* | ||
* @example | ||
* // Nested object structure | ||
* sanitizeOption({ | ||
* label: {label: "Documents", value: "123"}, | ||
* value: {label: "Documents", value: "123"} | ||
* }) // returns {label: "Documents", value: "123"} | ||
* | ||
* @example | ||
* // __lv wrapper | ||
* sanitizeOption({ | ||
* __lv: {label: "Test", value: "test-id"} | ||
* }) // returns {label: "Test", value: "test-id"} | ||
*/ | ||
export function sanitizeOption(option: unknown): { label: string; value: unknown } | string { | ||
if (typeof option === "string") return option; | ||
|
||
if (!option || typeof option !== "object") { | ||
return { | ||
label: "", | ||
value: "", | ||
}; | ||
} | ||
|
||
// If option has __lv wrapper, extract the inner option | ||
if ("__lv" in option) { | ||
const innerOption = (option as Record<string, unknown>).__lv; | ||
|
||
let actualLabel = ""; | ||
let actualValue = innerOption?.value; | ||
|
||
// Handle nested label in __lv | ||
if (innerOption?.label && typeof innerOption.label === "object" && "label" in innerOption.label) { | ||
actualLabel = String(innerOption.label.label || ""); | ||
} else { | ||
actualLabel = String(innerOption?.label || innerOption?.value || ""); | ||
} | ||
|
||
// Handle nested value in __lv | ||
if (innerOption?.value && typeof innerOption.value === "object" && "value" in innerOption.value) { | ||
actualValue = innerOption.value.value; | ||
} | ||
|
||
return { | ||
label: actualLabel, | ||
value: actualValue, | ||
}; | ||
} | ||
|
||
// Handle nested label and value objects | ||
const optionObj = option as Record<string, unknown>; | ||
let actualLabel = ""; | ||
let actualValue = optionObj.value; | ||
|
||
// Extract nested label | ||
if (optionObj.label && typeof optionObj.label === "object" && "label" in optionObj.label) { | ||
actualLabel = String(optionObj.label.label || ""); | ||
} else { | ||
actualLabel = String(optionObj.label || optionObj.value || ""); | ||
} | ||
|
||
// Extract nested value | ||
if (optionObj.value && typeof optionObj.value === "object" && "value" in optionObj.value) { | ||
actualValue = optionObj.value.value; | ||
} | ||
|
||
return { | ||
label: actualLabel, | ||
value: actualValue, | ||
}; | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.