Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
24 changes: 24 additions & 0 deletions packages/connect-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

# Changelog

# [1.3.0] - 2025-06-10

## Added
- Support for `externalUserId` parameter across all components as the preferred way to identify users
- Backward compatibility with existing `userId` parameter
- Proper user identification in SDK debugger for `configureComponent`, `reloadComponentProps`, and `getAccounts` calls

## Changed
- All internal SDK calls now use `externalUserId` parameter for consistency with backend SDK
- Account loading now properly includes user identification from form context

## Deprecated
- `userId` parameter in favor of `externalUserId` (existing code continues to work with console warnings)

## Migration
Replace `userId` with `externalUserId` in component props:
```typescript
// Before
<ComponentFormContainer userId={userId} />

// After (recommended)
<ComponentFormContainer externalUserId={userId} />
```

# [1.2.1] - 2025-06-07

- Fixing the SelectApp component to properly handle controlled values when not found in search results
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.1",
"version": "1.3.0",
"description": "Pipedream Connect library for React",
"files": [
"dist"
Expand Down
14 changes: 12 additions & 2 deletions packages/connect-react/src/components/ComponentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import type {
import { InternalComponentForm } from "./InternalComponentForm";

export type ComponentFormProps<T extends ConfigurableProps, U = ConfiguredProps<T>> = {
userId: string;
/**
* Your end user ID, for whom you're configuring the component.
*/
externalUserId?: string;
/**
* @deprecated Use `externalUserId` instead.
*/
userId?: string;
component: V1Component<T>;
configuredProps?: U; // XXX value?
disableQueryDisabling?: boolean;
Expand All @@ -22,7 +29,10 @@ export type ComponentFormProps<T extends ConfigurableProps, U = ConfiguredProps<
hideOptionalProps?: boolean;
sdkResponse?: unknown | undefined;
enableDebugging?: boolean;
};
} & (
| { externalUserId: string; userId?: never }
| { userId: string; externalUserId?: never }
);

export function ComponentForm<T extends ConfigurableProps>(props: ComponentFormProps<T>) {
return (
Expand Down
3 changes: 3 additions & 0 deletions packages/connect-react/src/components/ControlApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Select, { components as ReactSelectComponents } from "react-select";
import { useFrontendClient } from "../hooks/frontend-client-context";
import { useAccounts } from "../hooks/use-accounts";
import { useFormFieldContext } from "../hooks/form-field-context";
import { useFormContext } from "../hooks/form-context";
import { useCustomize } from "../hooks/customization-context";
import type { BaseReactSelectProps } from "../hooks/customization-context";
import { useMemo } from "react";
Expand All @@ -28,6 +29,7 @@ type ControlAppProps = {

export function ControlApp({ app }: ControlAppProps) {
const client = useFrontendClient();
const { externalUserId } = useFormContext();
const formFieldCtx = useFormFieldContext<ConfigurablePropApp>();
const {
id, prop, value, onChange,
Expand Down Expand Up @@ -73,6 +75,7 @@ export function ControlApp({ app }: ControlAppProps) {
refetch: refetchAccounts,
} = useAccounts(
{
externalUserId,
app: app.name_slug,
oauth_app_id: oauthAppId,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type RemoteOptionsContainerProps = {
export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerProps) {
const client = useFrontendClient();
const {
userId,
externalUserId,
component,
configurableProps,
configuredProps,
Expand Down Expand Up @@ -60,7 +60,7 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
configuredPropsUpTo[prop.name] = configuredProps[prop.name];
}
const componentConfigureInput: ComponentConfigureOpts = {
userId,
externalUserId,
page,
prevContext: context,
componentId: component.key,
Expand Down
33 changes: 26 additions & 7 deletions packages/connect-react/src/hooks/form-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import {
Observation, SdkError,
} from "../types";
import { resolveUserId } from "../utils/resolve-user-id";

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

Expand All @@ -39,6 +40,8 @@
setConfiguredProp: (idx: number, value: unknown) => void; // XXX type safety for value (T will rarely be static right?)
setSubmitting: (submitting: boolean) => void;
submitting: boolean;
externalUserId: string;
/** @deprecated Use externalUserId instead */
userId: string;
enableDebugging?: boolean;
};
Expand Down Expand Up @@ -77,8 +80,23 @@
const id = useId();

const {
component, configuredProps: __configuredProps, propNames, userId, sdkResponse, enableDebugging,
component, configuredProps: __configuredProps, propNames, externalUserId, userId, sdkResponse, enableDebugging,
} = formProps;

Check failure on line 85 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Trailing spaces not allowed
// Resolve user ID with deprecation warning
const { resolvedId: resolvedExternalUserId, warningType } = useMemo(() =>

Check failure on line 87 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Trailing spaces not allowed

Check failure on line 87 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Expected a line break before this closing brace

Check failure on line 87 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Expected a line break after this opening brace
resolveUserId(externalUserId, userId),

Check failure on line 88 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Trailing spaces not allowed
[externalUserId, userId]

Check failure on line 89 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Missing trailing comma

Check failure on line 89 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

A linebreak is required before ']'

Check failure on line 89 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

There should be a linebreak after this element

Check failure on line 89 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

A linebreak is required after '['

Check failure on line 89 in packages/connect-react/src/hooks/form-context.tsx

View workflow job for this annotation

GitHub Actions / Lint Code Base

Expected indentation of 2 spaces but found 4
);

// Show deprecation warnings in useEffect to avoid render side effects
useEffect(() => {
if (warningType === 'both') {
console.warn('[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning.');
} else if (warningType === 'deprecated') {
console.warn('[connect-react] userId is deprecated. Please use externalUserId instead.');
}
}, [warningType]);
const componentId = component.key;

const [
Expand Down Expand Up @@ -134,7 +152,7 @@
setReloadPropIdx,
] = useState<number>();
const componentReloadPropsInput: ReloadComponentPropsOpts = {
userId,
externalUserId: resolvedExternalUserId,
componentId,
configuredProps,
dynamicPropsId: dynamicProps?.id,
Expand Down Expand Up @@ -349,14 +367,14 @@
const [
prevUserId,
setPrevUserId,
] = useState(userId)
] = useState(resolvedExternalUserId)
useEffect(() => {
if (prevUserId !== userId) {
if (prevUserId !== resolvedExternalUserId) {
updateConfiguredProps({});
setPrevUserId(userId)
setPrevUserId(resolvedExternalUserId)
}
}, [
userId,
resolvedExternalUserId,
]);

// maybe should take prop as first arg but for text inputs didn't want to compute index each time
Expand Down Expand Up @@ -550,7 +568,8 @@
id,
isValid: !Object.keys(errors).length, // XXX want to expose more from errors
props: formProps,
userId,
externalUserId: resolvedExternalUserId,
userId: resolvedExternalUserId, // Keep for backward compatibility
component,
configurableProps,
configuredProps,
Expand Down
21 changes: 21 additions & 0 deletions packages/connect-react/src/utils/resolve-user-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Resolves user ID from either externalUserId or userId parameters
* Prefers externalUserId and returns both the resolved value and warning info
*/
export const resolveUserId = (
externalUserId?: string,
userId?: string
): { resolvedId: string; warningType?: 'both' | 'deprecated' } => {
if (externalUserId) {
if (userId) {
return { resolvedId: externalUserId, warningType: 'both' };
}
return { resolvedId: externalUserId };
}

if (userId) {
return { resolvedId: userId, warningType: 'deprecated' };
}

throw new Error('Either externalUserId or userId must be provided');
};
13 changes: 11 additions & 2 deletions pnpm-lock.yaml

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

Loading