├── .changeset └── config.json ├── .editorconfig ├── .eslintrc.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ ├── ci.yaml │ ├── dependency-review.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.yml ├── .prettierrc.yml ├── CONTRIBUTING.md ├── README.md ├── examples └── cra-demo │ ├── .env.local.template │ ├── .eslintrc.yml │ ├── README.md │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.tsx │ ├── CardExample.tsx │ ├── PaymentForm.tsx │ ├── React19.tsx │ ├── RecaptchaForm.tsx │ ├── SimpleForm.tsx │ ├── WithReactHookForm.tsx │ ├── index.css │ ├── index.tsx │ └── react-app-env.d.ts │ └── tsconfig.json ├── package.json ├── packages ├── formspree-core │ ├── .babelrc.json │ ├── .eslintrc.yml │ ├── .lintstagedrc.yml │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── src │ │ ├── base64.d.ts │ │ ├── base64.js │ │ ├── core.ts │ │ ├── env.d.ts │ │ ├── index.ts │ │ ├── session.ts │ │ ├── submission.ts │ │ └── utils.ts │ ├── test │ │ ├── core.test.ts │ │ ├── session.test.ts │ │ ├── submission.test.ts │ │ └── utils.test.ts │ └── tsconfig.json └── formspree-react │ ├── .babelrc.json │ ├── .eslintrc.yml │ ├── .lintstagedrc.yml │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── src │ ├── ValidationError.tsx │ ├── context.tsx │ ├── index.ts │ ├── stripe.tsx │ ├── types.ts │ ├── useForm.ts │ └── useSubmit.ts │ ├── test │ ├── ValidationError.test.tsx │ ├── __snapshots__ │ │ └── ValidationError.test.tsx.snap │ ├── context.test.tsx │ ├── mockStripe.ts │ ├── useForm.test.tsx │ └── useSubmit.test.tsx │ └── tsconfig.json ├── tsconfig.json ├── turbo.json └── yarn.lock /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["@formspree/cra-demo"] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Stop the editor from looking for .editorconfig files in the parent directories 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | max_line_length = 80 11 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | env: 3 | browser: true 4 | jest/globals: true 5 | extends: 6 | - eslint:recommended 7 | - prettier # eslint-config-prettier 8 | - plugin:@typescript-eslint/recommended 9 | plugins: 10 | - jest # eslint-plugin-jest 11 | rules: 12 | '@typescript-eslint/ban-ts-comment': off # temporary disable while addressing existing violations 13 | '@typescript-eslint/consistent-type-exports': error 14 | '@typescript-eslint/consistent-type-imports': error 15 | '@typescript-eslint/no-empty-function': off 16 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ for more info 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @formspree -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug/issue 3 | title: '[bug] ' 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! The more info you provide, the more we can help you. 9 | - type: checkboxes 10 | attributes: 11 | label: Is there an existing issue for this? 12 | description: Please search to see if an issue already exists for the bug you encountered. 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | 17 | - type: input 18 | attributes: 19 | label: Formspree React Version 20 | description: What version of Formspree React are you using? 21 | placeholder: 1.0.0 22 | validations: 23 | required: true 24 | 25 | - type: input 26 | attributes: 27 | label: Formspree Core Version 28 | description: What version of Formspree Core are you using? 29 | placeholder: 1.0.0 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | attributes: 35 | label: Current Behavior 36 | description: A concise description of what you're experiencing. 37 | validations: 38 | required: false 39 | 40 | - type: textarea 41 | attributes: 42 | label: Expected Behavior 43 | description: A concise description of what you expected to happen. 44 | validations: 45 | required: false 46 | 47 | - type: textarea 48 | attributes: 49 | label: Steps To Reproduce 50 | description: Steps or code snippets to reproduce the behavior. 51 | validations: 52 | required: false 53 | 54 | - type: input 55 | attributes: 56 | label: Link to Minimal Reproducible Example (CodeSandbox, StackBlitz, etc.) 57 | description: | 58 | This makes investigating issues and helping you out significantly easier! For most issues, you will likely get asked to provide one so why not add one now :) 59 | placeholder: https://codesandbox.io 60 | validations: 61 | required: false 62 | 63 | - type: textarea 64 | attributes: 65 | label: Anything else? 66 | description: | 67 | Browser info? Screenshots? Anything that will give us more context about the issue you are encountering! 68 | 69 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 70 | validations: 71 | required: false 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Open an issue 4 | url: https://github.com/formspree/formspree-react/issues/new 5 | about: Ask questions, suggest ideas or report your issue. 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | 4 | jobs: 5 | test: 6 | name: Test 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20.x 15 | cache: yarn 16 | cache-dependency-path: yarn.lock 17 | - run: yarn install --frozen-lockfile 18 | # We need to build first in order to generate the type declarations 19 | # in @formspree/core. 20 | - run: yarn build --filter="./packages/*" 21 | - run: yarn typecheck --filter="./packages/*" 22 | - run: yarn test --filter="./packages/*" 23 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: Dependency Review 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | - name: Dependency Review 20 | uses: actions/dependency-review-action@v4 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | 14 | # IMPORTANT: prevent this action from running on forks 15 | if: ${{ github.repository_owner == 'formspree' }} 16 | 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Repo 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup Node.js 20.x 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 20.x 26 | cache: yarn 27 | cache-dependency-path: yarn.lock 28 | 29 | - name: Install Dependencies 30 | run: yarn install --frozen-lockfile 31 | 32 | # This action will either create a new Release pull request with 33 | # all of the package versions and changelogs updated if there're 34 | # existing changesets or it will publish the packages to npm. 35 | - name: Create Release Pull Request or Publish to npm 36 | id: changesets 37 | uses: changesets/action@v1 38 | with: 39 | publish: yarn release 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage 8 | 9 | # CRA 10 | build 11 | 12 | # Rollup 13 | dist 14 | 15 | # misc 16 | .DS_Store 17 | *.pem 18 | 19 | # debug 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # local env files 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | .env 30 | 31 | # turbo 32 | .turbo 33 | 34 | .vscode -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.yml: -------------------------------------------------------------------------------- 1 | package.json: yarn sort-package-json 2 | '*.{json,md,yml}': yarn prettier --write 3 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Formspree Contribution Guide 2 | 3 | Thanks for your interest in contributing to Formspree! Please take a moment to review this document **before submitting a pull request.** 4 | 5 | If you want to contribute but aren't sure where to start, you can open a [new issue](https://github.com/formspree/formspree-react/issues). 6 | 7 | ## Prerequisites 8 | 9 | This project uses [`yarn v1`](https://yarnpkg.com/) as a package manager. 10 | 11 | ## Setting up your local repo 12 | 13 | Run the following commands from the root formspree-js directory: 14 | 15 | ```sh 16 | yarn 17 | yarn build # generate the artifact needed for depedendent packages 18 | ``` 19 | 20 | ## Development environment 21 | 22 | To play around with code while making changes, you can run the local development environment: 23 | 24 | ```sh 25 | yarn dev 26 | ``` 27 | 28 | This will run an example app ([`examples/cra-demo`](../examples/cra-demo)) on [localhost:3000](http://localhost:3000) that uses create-react-app. 29 | 30 | ## Development commands 31 | 32 | To run tests, typecheck on all packages: 33 | 34 | ```sh 35 | yarn test 36 | yarn typecheck 37 | ``` 38 | 39 | ## Opening a Pull Request 40 | 41 | When opening a pull request, include a changeset with your pull request: 42 | 43 | ```sh 44 | yarn changeset 45 | ``` 46 | 47 | The changeset files will be committed to main with the pull request. They will later be used for releasing a new version of a package. 48 | 49 | _Note: non-packages (examples/\*) do not need changesets._ 50 | 51 | ## Releasing a new version 52 | 53 | _Note: Only core maintainers can release new versions of formpree-js packages._ 54 | 55 | When pull requests are merged to `main`, a `release` Github Actions job will automatically generate a Version Package pull request, either creating a new one or updating an existing one. 56 | 57 | When we are ready to publish a new version for one or more packages, simply approve the Version Package pull request and merge it to `main`. 58 | 59 | Once the pull request is merged, the `release` GitHub Actions job will automatically tag and push the new versions of the affected packages and publish them to npm. 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formspree JS 2 | 3 | A monorepo containing libraries for seamless form integration with [Formspree](https://formspree.io/) via Javascript and/or React. 4 | 5 | ## Installation 6 | 7 | The core and react packages can be installed using your package manager of choice. 8 | 9 | ### `@formspree/core` 10 | 11 | ```sh 12 | npm install @formspree/core 13 | 14 | yarn add @formspree/core 15 | 16 | pnpm add @formspree/core 17 | ``` 18 | 19 | ### `@formspree/react` 20 | 21 | **Prerequisites** 22 | 23 | - React 16.8 or higher. 24 | 25 | ```sh 26 | npm install @formspree/react 27 | 28 | yarn add @formspree/react 29 | 30 | pnpm add @formspree/react 31 | ``` 32 | 33 | _Note: `@formspree/core` is a dependency of `@formspree/react`, so you don't need to install `@formspree/core` separately._ 34 | 35 | ## Help and Support 36 | 37 | For help and support please see [the Formspree React docs](https://help.formspree.io/hc/en-us/articles/360055613373). 38 | 39 | ## Contributing 40 | 41 | Please follow our [contributing guidelines](./CONTRIBUTING.md). 42 | -------------------------------------------------------------------------------- /examples/cra-demo/.env.local.template: -------------------------------------------------------------------------------- 1 | REACT_APP_SIMPLE_FORM_ID= 2 | REACT_APP_RECAPTCHA_FORM_ID= 3 | REACT_APP_RECAPTCHA_KEY= 4 | REACT_APP_PAYMENT_FORM_ID= 5 | REACT_APP_STRIPE_PUBLISHABLE_KEY= -------------------------------------------------------------------------------- /examples/cra-demo/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - '../../.eslintrc.yml' 4 | ignorePatterns: 5 | - jest.config.js 6 | - jest.setup.js 7 | - dist/ 8 | parserOptions: 9 | project: './tsconfig.json' 10 | -------------------------------------------------------------------------------- /examples/cra-demo/README.md: -------------------------------------------------------------------------------- 1 | # Formspree demo 2 | 3 | Quick simple demo with both a simple form and a payment form powered by Stripe 4 | 5 | ## Getting started 6 | 7 | 1. Create a Formspree account 8 | 2. Create 2 forms 9 | 3. Enable Stripe plugin on one of the forms 10 | 4. Run `cp .env.local.template .env.local` 11 | 5. Place your forms IDs and your Stripe publishable key that you can get from your Stripe plugin on Formspree plugins dashboard 12 | 6. Install dependencies 13 | 14 | ```bash 15 | yarn 16 | ``` 17 | 18 | 7. Start the Vite project 19 | 20 | ```bash 21 | yarn start 22 | ``` 23 | 24 | Happy coding! 25 | -------------------------------------------------------------------------------- /examples/cra-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@formspree/cra-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "react-scripts build", 7 | "clean": "rm -rf build && rm -rf node_modules", 8 | "dev": "react-scripts start", 9 | "eject": "react-scripts eject", 10 | "start": "react-scripts start" 11 | }, 12 | "browserslist": { 13 | "production": [ 14 | ">0.2%", 15 | "not dead", 16 | "not op_mini all" 17 | ], 18 | "development": [ 19 | "last 1 chrome version", 20 | "last 1 firefox version", 21 | "last 1 safari version" 22 | ] 23 | }, 24 | "dependencies": { 25 | "@formspree/react": "*", 26 | "react": "^19.0.0", 27 | "react-copy-to-clipboard": "^5.1.0", 28 | "react-dom": "^19.0.0", 29 | "react-google-recaptcha-v3": "^1.10.1", 30 | "react-hook-form": "^7.54.2", 31 | "react-scripts": "5.0.1", 32 | "web-vitals": "^3.4.0" 33 | }, 34 | "devDependencies": { 35 | "@types/react": "^19.0.0", 36 | "@types/react-copy-to-clipboard": "^5.0.7", 37 | "@types/react-dom": "^19.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/cra-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formspree/formspree-js/7c8334b144c9c4a2dc48f6736776b5af5e8580db/examples/cra-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/cra-demo/public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> 6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 | <meta name="theme-color" content="#000000" /> 8 | <meta 9 | name="description" 10 | content="Web site created using create-react-app" 11 | /> 12 | <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> 13 | <!-- 14 | manifest.json provides metadata used when your web app is installed on a 15 | user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 16 | --> 17 | <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> 18 | <!-- 19 | Notice the use of %PUBLIC_URL% in the tags above. 20 | It will be replaced with the URL of the `public` folder during the build. 21 | Only files inside the `public` folder can be referenced from the HTML. 22 | 23 | Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will 24 | work correctly both with client-side routing and a non-root public URL. 25 | Learn how to configure a non-root public URL by running `npm run build`. 26 | --> 27 | <title>Formspree demo 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/cra-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formspree/formspree-js/7c8334b144c9c4a2dc48f6736776b5af5e8580db/examples/cra-demo/public/logo192.png -------------------------------------------------------------------------------- /examples/cra-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formspree/formspree-js/7c8334b144c9c4a2dc48f6736776b5af5e8580db/examples/cra-demo/public/logo512.png -------------------------------------------------------------------------------- /examples/cra-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/cra-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/cra-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { FormspreeProvider } from '@formspree/react'; 2 | import { useState } from 'react'; 3 | 4 | import { React19 } from './React19'; 5 | import { WithReactHookForm } from './WithReactHookForm'; 6 | import PaymentForm from './PaymentForm'; 7 | import RecaptchaForm from './RecaptchaForm'; 8 | import SimpleForm from './SimpleForm'; 9 | 10 | enum Tab { 11 | React19 = 'react-19', 12 | ReactHookForm = 'react-hook-form', 13 | Recaptcha = 'recaptcha', 14 | Simple = 'simple', 15 | Stripe = 'stripe', 16 | } 17 | 18 | const App = () => { 19 | const [tab, setTab] = useState(Tab.Simple); 20 | 21 | return ( 22 | <> 23 |
24 |
25 | 32 | 39 | 47 | 54 | 61 |
62 | {tab === Tab.Stripe ? ( 63 | 66 | 67 | 68 | ) : tab === Tab.Recaptcha ? ( 69 | 70 | ) : tab === Tab.ReactHookForm ? ( 71 | 72 | ) : tab === Tab.React19 ? ( 73 | 74 | ) : ( 75 | 76 | )} 77 |
78 | 86 | 87 | ); 88 | }; 89 | 90 | export default App; 91 | -------------------------------------------------------------------------------- /examples/cra-demo/src/CardExample.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import CopyToClipboard from 'react-copy-to-clipboard'; 3 | 4 | type CardExampleProps = { 5 | title: string; 6 | cardNumber: string; 7 | }; 8 | 9 | const CardExample = ({ title, cardNumber }: CardExampleProps) => { 10 | const [isCopied, setCopied] = useState(false); 11 | 12 | return ( 13 |
  • 14 | {title} 15 | { 18 | setCopied(true); 19 | setTimeout(() => { 20 | setCopied(false); 21 | }, 1000); 22 | }} 23 | > 24 | 36 | 37 | {isCopied &&
    Copied!
    } 38 |
  • 39 | ); 40 | }; 41 | 42 | export default CardExample; 43 | -------------------------------------------------------------------------------- /examples/cra-demo/src/PaymentForm.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useForm, CardElement, ValidationError } from '@formspree/react'; 3 | import CardExample from './CardExample'; 4 | 5 | const useOptions = () => { 6 | const options = useMemo( 7 | () => ({ 8 | style: { 9 | base: { 10 | color: '#424770', 11 | letterSpacing: '0.025em', 12 | fontFamily: 'Source Code Pro, monospace', 13 | '::placeholder': { 14 | color: '#aab7c4', 15 | }, 16 | }, 17 | invalid: { 18 | color: '#9e2146', 19 | }, 20 | }, 21 | }), 22 | [] 23 | ); 24 | 25 | return options; 26 | }; 27 | 28 | const PaymentForm = () => { 29 | const options = useOptions(); 30 | const [state, handleSubmit] = useForm( 31 | process.env.REACT_APP_PAYMENT_FORM_ID as string 32 | ); 33 | 34 | return state && state.succeeded ? ( 35 |

    Payment has been handled successfully!

    36 | ) : ( 37 |
    38 |
    39 | 40 | 41 | 47 |
    48 |
    49 | 50 | 51 | 56 |
    57 |
    58 | 59 |
    60 | 63 | 64 |
    65 |

    You can use the following cards for testing:

    66 | 80 | Use any 3 digits for CVC and any future date for the date 81 | 86 | See more on Stripe 87 | 88 |
    89 |
    90 | ); 91 | }; 92 | 93 | export default PaymentForm; 94 | -------------------------------------------------------------------------------- /examples/cra-demo/src/React19.tsx: -------------------------------------------------------------------------------- 1 | import { isSubmissionError, type SubmissionResult } from '@formspree/core'; 2 | import { useSubmit, ValidationError } from '@formspree/react'; 3 | import { useActionState } from 'react'; 4 | 5 | export function React19() { 6 | const submit = useSubmit(process.env.REACT_APP_SIMPLE_FORM_ID as string); 7 | 8 | const [state, action, isPending] = useActionState< 9 | SubmissionResult | null, 10 | FormData 11 | >((_, inputs) => submit(inputs), null); 12 | 13 | if (state && !isSubmissionError(state)) { 14 | return

    Your message has been sent successfully!

    ; 15 | } 16 | 17 | return ( 18 |
    19 |
    20 | 21 | 22 | 28 |
    29 |
    30 | 31 | 32 |
    33 |
    34 | 35 |