Skip to content

Commit ae7aba0

Browse files
dannyrooseveltjoy-chanboop
authored andcommitted
Adding externalUserId to connect-react (PipedreamHQ#17063)
* Adding externalUserId to connect-react userId should be deprecated * linting fixes * PR feedback * Update CHANGELOG.md
1 parent 85ab74a commit ae7aba0

File tree

7 files changed

+106
-12
lines changed

7 files changed

+106
-12
lines changed

packages/connect-react/CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22

33
# Changelog
44

5+
# [1.3.0] - 2025-06-10
6+
7+
## Added
8+
9+
- Support for `externalUserId` param across all components as the preferred way to identify users
10+
- Backward compatibility with existing `userId` parameter
11+
12+
## Changed
13+
14+
- All internal SDK calls now use `externalUserId` parameter for consistency with backend SDK
15+
16+
## Deprecated
17+
18+
- `userId` parameter in favor of `externalUserId` (existing code continues to work with console warnings)
19+
20+
## Migration
21+
22+
Replace `userId` with `externalUserId` in component props:
23+
24+
```typescript
25+
// Before
26+
<ComponentFormContainer userId={userId} />
27+
28+
// After (recommended)
29+
<ComponentFormContainer externalUserId={userId} />
30+
```
31+
532
# [1.2.1] - 2025-06-07
633

734
- Fixing the SelectApp component to properly handle controlled values when not found in search results

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.2.1",
3+
"version": "1.3.0",
44
"description": "Pipedream Connect library for React",
55
"files": [
66
"dist"

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ import type {
1010
import { InternalComponentForm } from "./InternalComponentForm";
1111

1212
export type ComponentFormProps<T extends ConfigurableProps, U = ConfiguredProps<T>> = {
13-
userId: string;
13+
/**
14+
* Your end user ID, for whom you're configuring the component.
15+
*/
16+
externalUserId?: string;
17+
/**
18+
* @deprecated Use `externalUserId` instead.
19+
*/
20+
userId?: string;
1421
component: V1Component<T>;
1522
configuredProps?: U; // XXX value?
1623
disableQueryDisabling?: boolean;
@@ -22,7 +29,10 @@ export type ComponentFormProps<T extends ConfigurableProps, U = ConfiguredProps<
2229
hideOptionalProps?: boolean;
2330
sdkResponse?: unknown | undefined;
2431
enableDebugging?: boolean;
25-
};
32+
} & (
33+
| { externalUserId: string; userId?: never }
34+
| { userId: string; externalUserId?: never }
35+
);
2636

2737
export function ComponentForm<T extends ConfigurableProps>(props: ComponentFormProps<T>) {
2838
return (

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Select, { components as ReactSelectComponents } from "react-select";
22
import { useFrontendClient } from "../hooks/frontend-client-context";
33
import { useAccounts } from "../hooks/use-accounts";
44
import { useFormFieldContext } from "../hooks/form-field-context";
5+
import { useFormContext } from "../hooks/form-context";
56
import { useCustomize } from "../hooks/customization-context";
67
import type { BaseReactSelectProps } from "../hooks/customization-context";
78
import { useMemo } from "react";
@@ -28,6 +29,7 @@ type ControlAppProps = {
2829

2930
export function ControlApp({ app }: ControlAppProps) {
3031
const client = useFrontendClient();
32+
const { externalUserId } = useFormContext();
3133
const formFieldCtx = useFormFieldContext<ConfigurablePropApp>();
3234
const {
3335
id, prop, value, onChange,
@@ -73,6 +75,7 @@ export function ControlApp({ app }: ControlAppProps) {
7375
refetch: refetchAccounts,
7476
} = useAccounts(
7577
{
78+
externalUserId,
7679
app: app.name_slug,
7780
oauth_app_id: oauthAppId,
7881
},

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export type RemoteOptionsContainerProps = {
1313
export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerProps) {
1414
const client = useFrontendClient();
1515
const {
16-
userId,
16+
externalUserId,
1717
component,
1818
configurableProps,
1919
configuredProps,
@@ -60,7 +60,7 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
6060
configuredPropsUpTo[prop.name] = configuredProps[prop.name];
6161
}
6262
const componentConfigureInput: ComponentConfigureOpts = {
63-
userId,
63+
externalUserId,
6464
page,
6565
prevContext: context,
6666
componentId: component.key,

packages/connect-react/src/hooks/form-context.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import {
1717
Observation, SdkError,
1818
} from "../types";
19+
import { resolveUserId } from "../utils/resolve-user-id";
1920

2021
export type DynamicProps<T extends ConfigurableProps> = { id: string; configurableProps: T; }; // TODO
2122

@@ -39,6 +40,8 @@ export type FormContext<T extends ConfigurableProps> = {
3940
setConfiguredProp: (idx: number, value: unknown) => void; // XXX type safety for value (T will rarely be static right?)
4041
setSubmitting: (submitting: boolean) => void;
4142
submitting: boolean;
43+
externalUserId: string;
44+
/** @deprecated Use externalUserId instead */
4245
userId: string;
4346
enableDebugging?: boolean;
4447
};
@@ -77,8 +80,29 @@ export const FormContextProvider = <T extends ConfigurableProps>({
7780
const id = useId();
7881

7982
const {
80-
component, configuredProps: __configuredProps, propNames, userId, sdkResponse, enableDebugging,
83+
component, configuredProps: __configuredProps, propNames, externalUserId, userId, sdkResponse, enableDebugging,
8184
} = formProps;
85+
86+
// Resolve user ID with deprecation warning
87+
const {
88+
resolvedId: resolvedExternalUserId, warningType,
89+
} = useMemo(() => resolveUserId(externalUserId, userId), [
90+
externalUserId,
91+
userId,
92+
]);
93+
94+
// Show deprecation warnings in useEffect to avoid render side effects
95+
useEffect(() => {
96+
if (process.env.NODE_ENV !== "production") {
97+
if (warningType === "both") {
98+
console.warn("[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning.");
99+
} else if (warningType === "deprecated") {
100+
console.warn("[connect-react] userId is deprecated. Please use externalUserId instead.");
101+
}
102+
}
103+
}, [
104+
warningType,
105+
]);
82106
const componentId = component.key;
83107

84108
const [
@@ -134,7 +158,7 @@ export const FormContextProvider = <T extends ConfigurableProps>({
134158
setReloadPropIdx,
135159
] = useState<number>();
136160
const componentReloadPropsInput: ReloadComponentPropsOpts = {
137-
userId,
161+
externalUserId: resolvedExternalUserId,
138162
componentId,
139163
configuredProps,
140164
dynamicPropsId: dynamicProps?.id,
@@ -349,14 +373,14 @@ export const FormContextProvider = <T extends ConfigurableProps>({
349373
const [
350374
prevUserId,
351375
setPrevUserId,
352-
] = useState(userId)
376+
] = useState(resolvedExternalUserId)
353377
useEffect(() => {
354-
if (prevUserId !== userId) {
378+
if (prevUserId !== resolvedExternalUserId) {
355379
updateConfiguredProps({});
356-
setPrevUserId(userId)
380+
setPrevUserId(resolvedExternalUserId)
357381
}
358382
}, [
359-
userId,
383+
resolvedExternalUserId,
360384
]);
361385

362386
// maybe should take prop as first arg but for text inputs didn't want to compute index each time
@@ -550,7 +574,8 @@ export const FormContextProvider = <T extends ConfigurableProps>({
550574
id,
551575
isValid: !Object.keys(errors).length, // XXX want to expose more from errors
552576
props: formProps,
553-
userId,
577+
externalUserId: resolvedExternalUserId,
578+
userId: resolvedExternalUserId, // Keep for backward compatibility
554579
component,
555580
configurableProps,
556581
configuredProps,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Resolves user ID from either externalUserId or userId parameters
3+
* Prefers externalUserId and returns both the resolved value and warning info
4+
*/
5+
export const resolveUserId = (
6+
externalUserId?: string,
7+
userId?: string,
8+
): { resolvedId: string; warningType?: "both" | "deprecated" } => {
9+
if (externalUserId !== undefined && externalUserId !== "") {
10+
if (userId !== undefined && userId !== "") {
11+
return {
12+
resolvedId: externalUserId,
13+
warningType: "both",
14+
};
15+
}
16+
return {
17+
resolvedId: externalUserId,
18+
};
19+
}
20+
21+
if (userId !== undefined && userId !== "") {
22+
return {
23+
resolvedId: userId,
24+
warningType: "deprecated",
25+
};
26+
}
27+
28+
throw new Error("Either externalUserId or userId must be provided");
29+
};

0 commit comments

Comments
 (0)