@pipedream/connect-react
TypeScript icon, indicating that this package has built-in type declarations

1.0.2 • Public • Published

Pipedream Connect-React

This package is actively maintained and is continuing to expand supported capabilities and overall developer experience.

Please join our community or reach out to connect@pipedream.com with feedback.

Installation and usage

% npm install --save @pipedream/connect-react

Then use the ComponentFormContainer component in your app (see below for props and customization).

[!NOTE] To run the example below, set the following environment variables in .env.local.

PIPEDREAM_ALLOWED_ORIGINS

Make sure this matches the origin of your app, e.g.

# One domain — note the array
PIPEDREAM_ALLOWED_ORIGINS=["https://example.com"]

# Multiple domains
PIPEDREAM_ALLOWED_ORIGINS=["https://example.com", "http://localhost:3000"]

PIPEDREAM_CLIENT_ID

Create a Pipedream API OAuth client and enter its client ID here.

PIPEDREAM_CLIENT_SECRET

Your OAuth client secret. This is a secret, and should not be exposed to your frontend.

PIPEDREAM_PROJECT_ID

Create a Pipedream project and copy its ID.

/* actions.ts */
"use server";
import { createBackendClient } from "@pipedream/sdk/server";

const {
  NODE_ENV,
  PIPEDREAM_ALLOWED_ORIGINS,
  PIPEDREAM_CLIENT_ID,
  PIPEDREAM_CLIENT_SECRET,
  PIPEDREAM_PROJECT_ID,
} = process.env;

const allowedOrigins = JSON.parse(PIPEDREAM_ALLOWED_ORIGINS || "[]");

const client = createBackendClient({
  environment: NODE_ENV,
  projectId: PIPEDREAM_PROJECT_ID,
  credentials: {
    clientId: PIPEDREAM_CLIENT_ID,
    clientSecret: PIPEDREAM_CLIENT_SECRET,
  },
});

export async function fetchToken(opts: { externalUserId: string }) {
  return await client.createConnectToken({
    external_user_id: opts.externalUserId,
    allowed_origins: PIPEDREAM_ALLOWED_ORIGINS,
  });
}

/* page.tsx */
"use client";
import { useState } from "react";
import { createFrontendClient } from "@pipedream/sdk/browser";
import {
  ComponentFormContainer,
  FrontendClientProvider,
} from "@pipedream/connect-react";
import { fetchToken } from "./actions";

export default function Page() {
  // https://pipedream.com/docs/connect/api#external-users
  const userId = "my-authed-user-id";
  const client = createFrontendClient({
    environment: "development",
    externalUserId: userId,
    tokenCallback: fetchToken,
  });
  const [configuredProps, setConfiguredProps] = useState({
    text: "hello slack!",
  });
  return (
    <>
      <div>My application</div>
      <FrontendClientProvider client={client}>
        <ComponentFormContainer
          userId={userId}
          componentKey="slack-send-message"
          configuredProps={configuredProps}
          onUpdateConfiguredProps={setConfiguredProps}
        />
      </FrontendClientProvider>
    </>
  );
}

Components and Props

FrontendClientProvider

Necessary wrapper to provide the frontend client to other components.

type Props = {
  client: typeof import("@pipedream/sdk/browser").createFrontendClient;
};

ComponentFormContainer

Loader component for ComponentForm.

type ComponentFormContainerProps = {
  /** action (or trigger) to look up in the [Pipedream Registry](https://github.com/PipedreamHQ/pipedream/tree/master/components) */
  componentKey: string;
} & Omit<ComponentFormProps, "component">; // see below

ComponentForm

type ComponentFormProps = {
  component: typeof import("@pipedream/sdk").V1Component;
  /** External user configuring the form */
  userId: string;
  /** Form configured values */
  configuredProps?: Record<string, any>;
  /** Filtering configurable props */
  propNames?: string[];
  /** Shows submit button + callback when clicked */
  onSubmit: (ctx: FormContext) => Awaitable<void>;
  /** To control and store configured values on form updates, can be used to call actionRun or triggerDeploy */
  onUpdateConfiguredProps: (v: Record<string, any>) => void;
  /** Hide optional props section */
  hideOptionalProps: boolean;
  /** SDK response payload. Used in conjunction with enableDebugging to
   * show errors in the form. */
  sdkResponse: unknown[] | unknown | undefined;
  /** Whether to show show errors in the form. Requires sdkErrors to be set. */
  enableDebugging?: boolean;
};

Customization

Style individual components using the CustomizeProvider and a CustomizationConfig.

<FrontendClientProvider client={client}>
  <CustomizeProvider {...customizationConfig}>
    <ComponentFormContainer
      key="slack-send-message"
      configuredProps={configuredProps}
      onUpdateConfiguredProps={setConfiguredProps}
    />
  </CustomizeProvider>
</FrontendClientProvider>
type CustomizationConfig = {
  classNames?: CustomClassNamesConfig;
  classNamePrefix?: string;
  components?: CustomComponentsConfig;
  styles?: CustomStylesConfig;
  theme?: CustomThemeConfig;
  unstyled?: boolean;
};

The classNames prop

Not to be confused with the className prop, classNames takes an object with keys to represent the inner components of a ComponentForm. Each inner component takes a callback function with the following signature:

<CustomizeProvider
  classNames={{
    controlInput: ({ prop }) =>
      prop.type === "number" ? "border-red-600" : "border-blue-600",
  }}
/>

Note on CSS specificity

If you're using the classNames API and you're trying to override some base styles with the same level of specificity, you must ensure that your provided styles are declared later than the styles from @pipedream/connect-react (e.g. the link or style tag in the head of your HTML document) in order for them to take precedence.

The classNamePrefix prop

If you provide the classNamePrefix prop, all inner elements will be given a className with the provided prefix.

The components prop

You can rewrite individual inner components with the components prop. If you want to respect other customizations, such as styles, classNames or theme, use the useCustomize hook to integrate your base styles with the customization framework.

In this example, we're replacing the default Label inner component with a version that adds a checkmark to highlight required props.

import type { CSSProperties } from "react";
import type { ConfigurableProp, LabelProps } from "@pipedream/connect-react";
import { useCustomize } from "@pipedream/connect-react";

export function CustomLabel<T extends ConfigurableProp>(props: LabelProps<T>) {
  const { text, field } = props;
  const { id } = field;

  const { getProps, theme } = useCustomize();

  const baseStyles: CSSProperties = {
    color: theme.colors.neutral90,
    fontWeight: 450,
    gridArea: "label",
    textTransform: "capitalize",
    lineHeight: "1.5",
  };

  const required =
    !field.prop.optional && !["alert", "app"].includes(field.prop.type) ? (
      field.prop.type == "boolean" ? (
        typeof field.value != "undefined"
      ) : !!field.value ? (
        <span
          style={{ color: "#12b825", fontSize: "small", marginLeft: "0.5rem" }}
        >
          {" "}</span>
      ) : (
        <span
          style={{ color: "#d0d0d0", fontSize: "small", marginLeft: "0.5rem" }}
        >
          {" "}</span>
      )
    ) : (
      ""
    );
  return (
    <label htmlFor={id} {...getProps("label", baseStyles, props)}>
      {text}
      {required}
    </label>
  );
}

Then we apply the custom component using CustomizeProvider.

import { CustomLabel } from "./CustomLabel";

<CustomizeProvider
  components={{
    Label: CustomLabel,
  }}
/>;

The styles prop

The recommended way to provide custom styles is to use the styles prop. Each inner component takes either a CSSProperties object or a callback function with the following signature:

<CustomizeProvider
  styles={{
    label: { fontSize: "80%" },
    controlInput: (base, { theme }) => ({
      ...base,
      borderTop: 0,
      borderLeft: 0,
      borderRight: 0,
      border: "solid",
      borderColor: theme.colors.primary,
      backgroundColor: theme.colors.neutral0,
    }),
  }}
/>

Note that when using the callback function, the styles prop has access to theme customizations as well.

The theme prop

The default styles are derived from a theme object, which can be customized using the theme prop.

<CustomizeProvider
  theme={{
    borderRadius: 0,
    colors: {
      primary: "hsl(200, 100%, 60%)",
      primary75: "hsl(200, 100%, 55%)",
      primary50: "hsl(200, 100%, 40%)",
      primary25: "hsl(200, 100%, 35%)",

      danger: "#DE350B",
      dangerLight: "#FFBDAD",

      neutral0: "hsl(200, 50%, 97%)",
      neutral5: "hsl(200, 50%, 95%)",
      neutral10: "hsl(200, 50%, 90%)",
      neutral20: "hsl(200, 50%, 80%)",
      neutral30: "hsl(200, 50%, 70%)",
      neutral40: "hsl(200, 50%, 60%)",
      neutral50: "hsl(200, 50%, 50%)",
      neutral60: "hsl(200, 50%, 40%)",
      neutral70: "hsl(200, 50%, 30%)",
      neutral80: "hsl(200, 50%, 20%)",
      neutral90: "hsl(200, 50%, 10%)",
    },
    spacing: {
      baseUnit: 4,
      controlHeight: 10,
      menuGutter: 6,
    },
  }}
/>

The unstyled prop

While it is always possible to override default styling, you may prefer to start from completely unstyled components. Add the unstyled prop to remove all styling.

<CustomizeProvider unstyled={true} />

Inner components

The following list shows all of the customizable inner components used in a ComponentForm.

export type CustomizableProps = {
  componentForm: ComponentProps<typeof ComponentForm>;
  connectButton: ComponentProps<typeof ControlApp> &
    FormFieldContext<ConfigurableProp>;
  controlAny: ComponentProps<typeof ControlAny> &
    FormFieldContext<ConfigurableProp>;
  controlApp: ComponentProps<typeof ControlApp> &
    FormFieldContext<ConfigurableProp>;
  controlBoolean: ComponentProps<typeof ControlBoolean> &
    FormFieldContext<ConfigurableProp>;
  controlInput: ComponentProps<typeof ControlInput> &
    FormFieldContext<ConfigurableProp>;
  controlSubmit: ComponentProps<typeof ControlSubmit>;
  description: ComponentProps<typeof Description>;
  error: ComponentProps<typeof Errors>;
  errors: ComponentProps<typeof Errors>;
  field: ComponentProps<typeof Field>;
  heading: ComponentProps<typeof ComponentForm>;
  label: ComponentProps<typeof Label>;
  optionalFields: ComponentProps<typeof ComponentForm>;
  optionalFieldButton: ComponentProps<typeof OptionalFieldButton>;
};

Internally, @pipedream/connect-react uses react-select to implement complex dropdown components.

export type ReactSelectComponents = {
  controlAppSelect: typeof ControlApp;
  controlSelect: typeof ControlSelect;
};

Customizing dropdown components is very similar to customizing other inner components except each dropdown supports deeper customization of elements within react-select

<CustomizeProvider
  styles={{
    controlSelect: {
      control: (base, { theme }) => ({
        ...base,
        borderRadius: 0,
        borderColor: theme.colors.primary25,
        fontSize: "small",
        maxHeight: "36px",
      }),
    },
  }}
/>

See React Select for more details on the customization options available.

Hooks

  • useCustomizesee above
  • useFormContext
  • useFormFieldContext
  • useFrontendClientallows use of provided Pipedream frontendClient
  • useAccountsreact-query wrapper to list Pipedream connect accounts (for app, external user, etc.)
  • useAppreact-query wrapper to retrieve a Pipedream app
  • useAppsreact-query wrapper to list Pipedream apps
  • useComponentreact-query wrapper to retrieve a Pipedream component (action or trigger)
  • useComponentsreact-query wrapper to list Pipedream components (actions or triggers)

See hooks folder for details.

Readme

Keywords

none

Package Sidebar

Install

npm i @pipedream/connect-react

Weekly Downloads

1,243

Version

1.0.2

License

MIT

Unpacked Size

851 kB

Total Files

6

Last publish

Collaborators

  • sacerdoti
  • casret
  • tjk
  • adolfo-pd
  • danpipedream
  • jasonatpipedream
  • alan-pipedream