diff --git a/packages/connect-react/CHANGELOG.md b/packages/connect-react/CHANGELOG.md
index 91eb9e0021eed..354ab2afcd45b 100644
--- a/packages/connect-react/CHANGELOG.md
+++ b/packages/connect-react/CHANGELOG.md
@@ -2,6 +2,33 @@
# Changelog
+# [1.3.0] - 2025-06-10
+
+## Added
+
+- Support for `externalUserId` param across all components as the preferred way to identify users
+- Backward compatibility with existing `userId` parameter
+
+## Changed
+
+- All internal SDK calls now use `externalUserId` parameter for consistency with backend SDK
+
+## 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
+
+
+// After (recommended)
+
+```
+
# [1.2.1] - 2025-06-07
- Fixing the SelectApp component to properly handle controlled values when not found in search results
diff --git a/packages/connect-react/package.json b/packages/connect-react/package.json
index 84c28e1962c22..412efcbabae97 100644
--- a/packages/connect-react/package.json
+++ b/packages/connect-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@pipedream/connect-react",
- "version": "1.2.1",
+ "version": "1.3.0",
"description": "Pipedream Connect library for React",
"files": [
"dist"
diff --git a/packages/connect-react/src/components/ComponentForm.tsx b/packages/connect-react/src/components/ComponentForm.tsx
index 47c19f9cac8f2..2ebe0b2dbaabf 100644
--- a/packages/connect-react/src/components/ComponentForm.tsx
+++ b/packages/connect-react/src/components/ComponentForm.tsx
@@ -10,7 +10,14 @@ import type {
import { InternalComponentForm } from "./InternalComponentForm";
export type ComponentFormProps> = {
- userId: string;
+ /**
+ * Your end user ID, for whom you're configuring the component.
+ */
+ externalUserId?: string;
+ /**
+ * @deprecated Use `externalUserId` instead.
+ */
+ userId?: string;
component: V1Component;
configuredProps?: U; // XXX value?
disableQueryDisabling?: boolean;
@@ -22,7 +29,10 @@ export type ComponentFormProps(props: ComponentFormProps) {
return (
diff --git a/packages/connect-react/src/components/ControlApp.tsx b/packages/connect-react/src/components/ControlApp.tsx
index cfdf0a063207e..0535ce881c00e 100644
--- a/packages/connect-react/src/components/ControlApp.tsx
+++ b/packages/connect-react/src/components/ControlApp.tsx
@@ -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";
@@ -28,6 +29,7 @@ type ControlAppProps = {
export function ControlApp({ app }: ControlAppProps) {
const client = useFrontendClient();
+ const { externalUserId } = useFormContext();
const formFieldCtx = useFormFieldContext();
const {
id, prop, value, onChange,
@@ -73,6 +75,7 @@ export function ControlApp({ app }: ControlAppProps) {
refetch: refetchAccounts,
} = useAccounts(
{
+ externalUserId,
app: app.name_slug,
oauth_app_id: oauthAppId,
},
diff --git a/packages/connect-react/src/components/RemoteOptionsContainer.tsx b/packages/connect-react/src/components/RemoteOptionsContainer.tsx
index d606bee52f6c0..5dd526f79265b 100644
--- a/packages/connect-react/src/components/RemoteOptionsContainer.tsx
+++ b/packages/connect-react/src/components/RemoteOptionsContainer.tsx
@@ -13,7 +13,7 @@ export type RemoteOptionsContainerProps = {
export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerProps) {
const client = useFrontendClient();
const {
- userId,
+ externalUserId,
component,
configurableProps,
configuredProps,
@@ -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,
diff --git a/packages/connect-react/src/hooks/form-context.tsx b/packages/connect-react/src/hooks/form-context.tsx
index e78ba540919a3..d516615bda0aa 100644
--- a/packages/connect-react/src/hooks/form-context.tsx
+++ b/packages/connect-react/src/hooks/form-context.tsx
@@ -16,6 +16,7 @@ import {
import {
Observation, SdkError,
} from "../types";
+import { resolveUserId } from "../utils/resolve-user-id";
export type DynamicProps = { id: string; configurableProps: T; }; // TODO
@@ -39,6 +40,8 @@ export type FormContext = {
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;
};
@@ -77,8 +80,29 @@ export const FormContextProvider = ({
const id = useId();
const {
- component, configuredProps: __configuredProps, propNames, userId, sdkResponse, enableDebugging,
+ component, configuredProps: __configuredProps, propNames, externalUserId, userId, sdkResponse, enableDebugging,
} = formProps;
+
+ // Resolve user ID with deprecation warning
+ const {
+ resolvedId: resolvedExternalUserId, warningType,
+ } = useMemo(() => resolveUserId(externalUserId, userId), [
+ externalUserId,
+ userId,
+ ]);
+
+ // Show deprecation warnings in useEffect to avoid render side effects
+ useEffect(() => {
+ if (process.env.NODE_ENV !== "production") {
+ 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 [
@@ -134,7 +158,7 @@ export const FormContextProvider = ({
setReloadPropIdx,
] = useState();
const componentReloadPropsInput: ReloadComponentPropsOpts = {
- userId,
+ externalUserId: resolvedExternalUserId,
componentId,
configuredProps,
dynamicPropsId: dynamicProps?.id,
@@ -349,14 +373,14 @@ export const FormContextProvider = ({
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
@@ -550,7 +574,8 @@ export const FormContextProvider = ({
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,
diff --git a/packages/connect-react/src/utils/resolve-user-id.ts b/packages/connect-react/src/utils/resolve-user-id.ts
new file mode 100644
index 0000000000000..ee269ba96a168
--- /dev/null
+++ b/packages/connect-react/src/utils/resolve-user-id.ts
@@ -0,0 +1,29 @@
+/**
+ * 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 !== undefined && externalUserId !== "") {
+ if (userId !== undefined && userId !== "") {
+ return {
+ resolvedId: externalUserId,
+ warningType: "both",
+ };
+ }
+ return {
+ resolvedId: externalUserId,
+ };
+ }
+
+ if (userId !== undefined && userId !== "") {
+ return {
+ resolvedId: userId,
+ warningType: "deprecated",
+ };
+ }
+
+ throw new Error("Either externalUserId or userId must be provided");
+};