Skip to content

Commit b51fc2d

Browse files
Adding externalUserId to connect-react
userId should be deprecated
1 parent 8616b6d commit b51fc2d

File tree

8 files changed

+100
-14
lines changed

8 files changed

+100
-14
lines changed

packages/connect-react/CHANGELOG.md

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

33
# Changelog
44

5+
# [1.3.0] - 2025-06-10
6+
7+
## Added
8+
- Support for `externalUserId` parameter across all components as the preferred way to identify users
9+
- Backward compatibility with existing `userId` parameter
10+
- Proper user identification in SDK debugger for `configureComponent`, `reloadComponentProps`, and `getAccounts` calls
11+
12+
## Changed
13+
- All internal SDK calls now use `externalUserId` parameter for consistency with backend SDK
14+
- Account loading now properly includes user identification from form context
15+
16+
## Deprecated
17+
- `userId` parameter in favor of `externalUserId` (existing code continues to work with console warnings)
18+
19+
## Migration
20+
Replace `userId` with `externalUserId` in component props:
21+
```typescript
22+
// Before
23+
<ComponentFormContainer userId={userId} />
24+
25+
// After (recommended)
26+
<ComponentFormContainer externalUserId={userId} />
27+
```
28+
529
# [1.2.1] - 2025-06-07
630

731
- 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: 26 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,23 @@ 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 { resolvedId: resolvedExternalUserId, warningType } = useMemo(() =>
88+
resolveUserId(externalUserId, userId),
89+
[externalUserId, userId]
90+
);
91+
92+
// Show deprecation warnings in useEffect to avoid render side effects
93+
useEffect(() => {
94+
if (warningType === 'both') {
95+
console.warn('[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning.');
96+
} else if (warningType === 'deprecated') {
97+
console.warn('[connect-react] userId is deprecated. Please use externalUserId instead.');
98+
}
99+
}, [warningType]);
82100
const componentId = component.key;
83101

84102
const [
@@ -134,7 +152,7 @@ export const FormContextProvider = <T extends ConfigurableProps>({
134152
setReloadPropIdx,
135153
] = useState<number>();
136154
const componentReloadPropsInput: ReloadComponentPropsOpts = {
137-
userId,
155+
externalUserId: resolvedExternalUserId,
138156
componentId,
139157
configuredProps,
140158
dynamicPropsId: dynamicProps?.id,
@@ -349,14 +367,14 @@ export const FormContextProvider = <T extends ConfigurableProps>({
349367
const [
350368
prevUserId,
351369
setPrevUserId,
352-
] = useState(userId)
370+
] = useState(resolvedExternalUserId)
353371
useEffect(() => {
354-
if (prevUserId !== userId) {
372+
if (prevUserId !== resolvedExternalUserId) {
355373
updateConfiguredProps({});
356-
setPrevUserId(userId)
374+
setPrevUserId(resolvedExternalUserId)
357375
}
358376
}, [
359-
userId,
377+
resolvedExternalUserId,
360378
]);
361379

362380
// maybe should take prop as first arg but for text inputs didn't want to compute index each time
@@ -550,7 +568,8 @@ export const FormContextProvider = <T extends ConfigurableProps>({
550568
id,
551569
isValid: !Object.keys(errors).length, // XXX want to expose more from errors
552570
props: formProps,
553-
userId,
571+
externalUserId: resolvedExternalUserId,
572+
userId: resolvedExternalUserId, // Keep for backward compatibility
554573
component,
555574
configurableProps,
556575
configuredProps,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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) {
10+
if (userId) {
11+
return { resolvedId: externalUserId, warningType: 'both' };
12+
}
13+
return { resolvedId: externalUserId };
14+
}
15+
16+
if (userId) {
17+
return { resolvedId: userId, warningType: 'deprecated' };
18+
}
19+
20+
throw new Error('Either externalUserId or userId must be provided');
21+
};

pnpm-lock.yaml

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)