Skip to content

Commit

Permalink
feat: adds createPlaidLink hook and Plaid Link components
Browse files Browse the repository at this point in the history
  • Loading branch information
thedanchez committed Nov 25, 2024
1 parent 9b60836 commit 73e0119
Show file tree
Hide file tree
Showing 19 changed files with 1,328 additions and 62 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ jobs:
run: bun run lint
- name: Format Check
run: bun run format
- name: Run Tests
run: bun run test
163 changes: 121 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,137 @@
# Template: SolidJS Library
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Ecosystem&background=tiles&project=solid-plaid-link" alt="solid-plaid-link">
</p>

Template for [SolidJS](https://www.solidjs.com/) library package. Bundling of the library is managed by [tsup](https://tsup.egoist.dev/).
[![NPM Version](https://img.shields.io/npm/v/solid-plaid-link.svg?style=for-the-badge)](https://www.npmjs.com/package/solid-plaid-link) [![Build Status](https://img.shields.io/github/actions/workflow/status/thedanchez/solid-plaid-link/ci.yaml?branch=main&logo=github&style=for-the-badge)](https://github.com/thedanchez/solid-plaid-link/actions/workflows/ci.yaml) [![bun](https://img.shields.io/badge/maintained%20with-bun-cc00ff.svg?style=for-the-badge&logo=bun)](https://bun.sh/)

Other things configured include:
# Solid Plaid Link

- Bun (for dependency management and running scripts)
- TypeScript
- ESLint / Prettier
- Solid Testing Library + Vitest (for testing)
- Playground app using library
- GitHub Actions (for all CI/CD)
Library for integrating with [Plaid Link](https://plaid.com/docs/link/) in your SolidJS applications.

## Getting Started
_Note: This is an unofficial Solid fork based on the official [react-plaid-link](https://github.com/plaid/react-plaid-link) library._

Some pre-requisites before install dependencies:

- Install Node Version Manager (NVM)
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
```
- Install Bun
```bash
curl -fsSL https://bun.sh/install | bash
```

### Installing Dependencies
### Installation

```bash
nvm use
bun install
npm install solid-js @danchez/solid-plaid-link
pnpm add solid-js @danchez/solid-plaid-link
yarn add solid-js @danchez/solid-plaid-link
bun add solid-js @danchez/solid-plaid-link
```

### Local Development Build

```bash
bun start
```
## Summary

### Linting & Formatting
This library exports three things:

```bash
bun run lint # checks source for lint violations
bun run format # checks source for format violations
```tsx
import { createPlaidLink, PlaidLink, PlaidEmbeddedLink } from "@danchez/solid-plaid-link";
```

bun run lint:fix # fixes lint violations
bun run format:fix # fixes format violations
### createPlaidLink

The main core of the library -- this hook does all the heavy lifting for `solid-plaid-link`. It is responsibile for dynamically loading the Plaid script and managing the Plaid Link lifecycle for you. It takes care of refreshing the link token used to initialize Plaid Link before it expires (after 4 hours) and destroying the Plaid Link UI when unmounting on your behalf. Use this hook when you want full control over how and when to display the Plaid Link UI.

- _Note: A new Plaid Link instance is created any time the configuration props change._

In order to initialize Plaid Link via this hook, a [link token](https://plaid.com/docs/api/link/#linktokencreate) is required from Plaid. You can fetch the link token from your server via the required `fetchToken` field. Once Link has been initialized, it returns a temporary `publicToken`. This `publicToken` must then be exchanged for a permanent `accessToken` which is used to make product requests.

- _Note: the `publicToken` to `accessToken` exchange should be handled by your API server that fulfills requests for your SPA._

```tsx
import { createEffect, Match, onMount, Switch } from "solid-js";
import { createPlaidLink } from "@danchez/solid-plaid-link";

const ExampleOne = () => {
const { ready, error, plaidLink } = createPlaidLink(() => ({
fetchToken: async () => {
const response = await fetch("https://api.example.com/plaid/link-token");
const { link_token, expiration } = await response.json();
return { link_token, expiration };
},
onLoad: () => { ... },
onSuccess: (publicToken, metaData) => { ... },
onEvent: (eventName, metaData) => { ... },
onExit: (error, metaData) => { ... },
}));

return (
<Switch>
<Match when={error()}>{error().message}</Match>
<Match when={ready()}>
{ /* use <PlaidLink /> if you just need a button :) */ }
<button onClick={() => { plaidLink().open(); }}>
Open Plaid Link
</button>
</Match>
</Switch>
);
};

const ExampleTwo = () => {
const { ready, error, plaidLink } = createPlaidLink(() => ({
fetchToken: async () => {
const response = await fetch("https://api.example.com/plaid/link-token");
const { link_token, expiration } = await response.json();
return { link_token, expiration };
},
onLoad: () => { ... },
onSuccess: (publicToken, metaData) => { ... },
onEvent: (eventName, metaData) => { ... },
onExit: (error, metaData) => { ... },
}));

createEffect(() => {
if (!ready()) return;
plaidLink().open();
});

createEffect(() => {
if (!error()) return;
// handle error
});

return (...);
};
```

### Contributing
### PlaidLink / PlaidEmbeddedLink

This library also provides two Solid components as a convenience: `<PlaidLink />` and `<PlaidEmbeddedLink />` .

`<PlaidLink />` is a button which opens the Plaid Link UI on click. If there are any issues downloading Plaid or creating the Plaid Link instance, the button will be disabled. It is built on top of `createPlaidLink` so it accepts all the same configuration fields along with all the `ButtonHTMLAttributes` as props so you are free to customize the button with your own styles. Additionally, you can enrich the `disabled` or `onClick` props with your own logic on top of their underlying default behaviors.

`<PlaidEmbeddedLink />` is a component that renders the embedded version of the Plaid Link UI using a `div` container. It accepts the same Plaid configuration options as `PlaidLink` but it is **not** built on top of `createPlaidLink` as `PlaidLink` is since the underlying `Plaid.createEmbedded` API works a bit differently than the `Plaid.create` API.

One thing to note about the aforementioned Solid components above, specifically with regard to `fetchToken`: in Solid, JSX is a reactively tracked scope and the reactivity system in Solid only tracks **_synchronously_** -- therefore, components cannot receive `async` functions as values. To workaround this, the function passed to `fetchToken` for the Plaid Link JSX components must use promise chaining instead of relying on `async/await` syntax.

```tsx
import { PlaidLink, PlaidEmbeddedLink } from "@danchez/solid-plaid-link";

const ComponentA = () => (
<PlaidLink
fetchToken={() => fetch("https://api.example.com/plaid/link-token").then((response) => response.json())}
onLoad={() => { ... }}
onLoadError={(error) => { ... }}
onSuccess={(publicToken, metaData) => { ... }}
onEvent={(eventName, metaData) => { ... }}
onExit={(error, metaData) => { ... }}
>
Open Plaid
</PlaidLink>
);

const ComponentB = () => (
<PlaidEmbeddedLink
fetchToken={() => fetch("https://api.example.com/plaid/link-token").then((response) => response.json())}
onLoad={() => { ... }}
onLoadError={(error) => { ... }}
onSuccess={(publicToken, metaData) => { ... }}
onEvent={(eventName, metaData) => { ... }}
onExit={(error, metaData) => { ... }}
/>
);
```

The only requirements when contributing are:
## Feedback

- You keep a clean git history in your branch
- rebasing `main` instead of making merge commits.
- Using proper commit message formats that adhere to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
- Additionally, squashing (via rebase) commits that are not [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
- CI checks pass before merging into `main`
Feel free to post any issues or suggestions to help improve this library.
Binary file modified bun.lockb
Binary file not shown.
39 changes: 35 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
{
"name": "solid-plaid-link",
"version": "0.1.0",
"description": "Solid bindings for Plaid Link",
"name": "@danchez/solid-plaid-link",
"version": "1.0.0",
"description": "Library for integrating Plaid Link into SolidJS applications.",
"type": "module",
"author": "Daniel Sanchez <dsanc89@icloud.com>",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"browser": {},
"exports": {
"solid": "./dist/index.jsx",
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"license": "MIT",
"homepage": "https://github.com/thedanchez/solid-plaid-link#readme",
"bugs": {
"url": "https://github.com/thedanchez/solid-plaid-link/issues"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"link",
"plaid",
"plaid-link",
"solid",
"solid-plaid"
],
"scripts": {
"build": "tsup",
"build:watch": "tsup --watch",
Expand All @@ -26,6 +51,7 @@
"devDependencies": {
"@solidjs/testing-library": "^0.8.10",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/user-event": "^14.5.2",
"@types/bun": "^1.1.10",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
Expand All @@ -35,6 +61,7 @@
"eslint-plugin-solid": "^0.14.3",
"jsdom": "^25.0.1",
"prettier": "^3.3.3",
"solid-js": "1.9.3",
"tsup": "^8.3.0",
"tsup-preset-solid": "^2.2.0",
"typescript": "^5.6.2",
Expand All @@ -44,5 +71,9 @@
},
"peerDependencies": {
"solid-js": ">=1.8.0"
}
},
"dependencies": {
"solid-create-script": "^1.0.0"
},
"typesVersions": {}
}
35 changes: 30 additions & 5 deletions playground/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import { createSignal } from "solid-js";
import { createScript } from "solid-create-script";
import { createEffect, createSignal, Show } from "solid-js";

import { Second } from "./Second";
import { Third } from "./Third";

export const App = () => {
const [count, setCount] = createSignal(0);
const [showSecond, setShowSecond] = createSignal(false);
const [showThird, setShowThird] = createSignal(false);

const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });

createEffect(() => {
console.log("Script Error State: ", script.error.message);
});

return (
<div>
<div>Playground App</div>
<div>Count: {count()}</div>
<div>Script Loading State: {script.loading.toString()}</div>
<div>Script Error: {script.error?.message}</div>
<button
onClick={() => {
setShowSecond((prev) => !prev);
}}
>
Toggle Second
</button>
<button
onClick={() => {
setCount((prev) => prev + 1);
setShowThird((prev) => !prev);
}}
>
Increment Count
Toggle Third
</button>
<Show when={showSecond()}>
<Second />
</Show>
<Show when={showThird()}>
<Third />
</Show>
</div>
);
};
12 changes: 12 additions & 0 deletions playground/Second.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createScript } from "solid-create-script";

export const Second = () => {
const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });

return (
<div>
<div>Second Component</div>
<div>Script Loading State: {script.loading.toString()}</div>
</div>
);
};
12 changes: 12 additions & 0 deletions playground/Third.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createScript } from "solid-create-script";

export const Third = () => {
const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });

return (
<div>
<div>Third Component</div>
<div>Script Loading State: {script.loading.toString()}</div>
</div>
);
};
Loading

0 comments on commit 73e0119

Please sign in to comment.