├── .husky └── pre-commit ├── .codeclimate.yml ├── .commitlintrc.js ├── babel.config.js ├── tsconfig.build.json ├── .prettierrc ├── tests ├── const.ts ├── errors.test.ts └── useMailChimpForm │ ├── handleSubmitFetchJsonpReject.test.ts │ ├── handleSubmitFetchJsonpException.test.ts │ ├── handleSubmitFail.test.ts │ ├── renderHooks.test.ts │ └── handleSubmitSuccess.test.ts ├── api-docs ├── globals.md ├── interfaces │ └── Params.md ├── variables │ ├── useFormFields.md │ └── useMailChimpForm.md └── README.md ├── typedoc.json ├── .eslintrc.js ├── renovate.json ├── .github ├── dependabot.yml └── workflows │ ├── test.yml │ └── npm-release.yml ├── .releaserc.json ├── .releaserc.js ├── src ├── errors.ts └── index.ts ├── vite.config.ts ├── jest.config.js ├── LICENSE ├── tsconfig.json ├── eslint.config.js ├── package.json ├── README.md ├── .gitignore └── docs └── CHANGELOG.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npx --no -- lint-staged 4 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | method-lines: 4 | config: 5 | threshold: 50 6 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "body-max-line-length": [2, "always", 100], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "tests", "**/*.test.ts", "**/*.test.tsx"], 4 | "include": ["src", "tsup.config.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid" 10 | } 11 | -------------------------------------------------------------------------------- /tests/const.ts: -------------------------------------------------------------------------------- 1 | export const TEST_MAILCHIMP_URL = 2 | "https://example.us20.list-manage.com/subscribe/post?u=example&id=example"; 3 | 4 | export const TEST_EMAIL = "test@example.com"; 5 | 6 | export const TEST_MESSAGE = "TESTING Message"; 7 | -------------------------------------------------------------------------------- /api-docs/globals.md: -------------------------------------------------------------------------------- 1 | [**use-mailchimp-form v3.1.3**](README.md) 2 | 3 | --- 4 | 5 | # use-mailchimp-form v3.1.3 6 | 7 | ## Interfaces 8 | 9 | - [Params](interfaces/Params.md) 10 | 11 | ## Variables 12 | 13 | - [useFormFields](variables/useFormFields.md) 14 | - [useMailChimpForm](variables/useMailChimpForm.md) 15 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "out": "api-docs", 4 | "excludePrivate": true, 5 | "excludeProtected": true, 6 | "excludeExternals": true, 7 | "includeVersion": true, 8 | "theme": "default", 9 | "name": "use-mailchimp-form", 10 | "readme": "README.md", 11 | "plugin": ["typedoc-plugin-markdown"] 12 | } 13 | -------------------------------------------------------------------------------- /api-docs/interfaces/Params.md: -------------------------------------------------------------------------------- 1 | [**use-mailchimp-form v3.1.3**](../README.md) 2 | 3 | --- 4 | 5 | [use-mailchimp-form](../globals.md) / Params 6 | 7 | # Interface: Params 8 | 9 | Defined in: [index.ts:6](https://github.com/imgarylai/use-mailchimp-form/blob/586051af57e37bb69b621a3d3314d1f3b5b97eea/src/index.ts#L6) 10 | 11 | ## Indexable 12 | 13 | \[`key`: `string`\]: `unknown` 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | ], 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:react/recommended", 11 | "plugin:react-hooks/recommended", 12 | "prettier" 13 | ], 14 | settings: { 15 | react: { 16 | version: "detect" 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "automerge": true, 4 | "automergeStrategy": "rebase", 5 | "extends": ["config:recommended"], 6 | "schedule": ["every weekend"], 7 | "packageRules": [ 8 | { 9 | "groupName": "node", 10 | "matchPackageNames": ["node"], 11 | "enabled": false 12 | }, 13 | { 14 | "matchUpdateTypes": ["minor", "patch"], 15 | "groupName": "non-major dependencies", 16 | "automerge": true, 17 | "schedule": ["every weekend"] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | [ 7 | "@semantic-release/changelog", 8 | { 9 | "changelogFile": "docs/CHANGELOG.md" 10 | } 11 | ], 12 | "@semantic-release/npm", 13 | [ 14 | "@semantic-release/git", 15 | { 16 | "assets": ["docs/CHANGELOG.md", "package.json", "package-lock.json"], 17 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 18 | } 19 | ], 20 | [ 21 | "@semantic-release/github", 22 | { 23 | "successComment": false, 24 | "failTitle": false 25 | } 26 | ] 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: [ 3 | "main", 4 | { 5 | name: "develop", 6 | prerelease: true, 7 | }, 8 | ], 9 | plugins: [ 10 | "@semantic-release/commit-analyzer", 11 | "@semantic-release/release-notes-generator", 12 | [ 13 | "@semantic-release/changelog", 14 | { 15 | changelogFile: "docs/CHANGELOG.md", 16 | }, 17 | ], 18 | "@semantic-release/npm", 19 | "@semantic-release/github", 20 | [ 21 | "@semantic-release/git", 22 | { 23 | assets: ["docs/CHANGELOG.md", "package.json", "package-lock.json"], 24 | message: 25 | "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}", 26 | }, 27 | ], 28 | ], 29 | }; 30 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | export type ErrorWithMessage = { 2 | message: string; 3 | }; 4 | 5 | export const isErrorWithMessage = (error: unknown): error is ErrorWithMessage => 6 | typeof error === "object" && 7 | error !== null && 8 | "message" in error && 9 | typeof (error as Record)["message"] === "string"; 10 | 11 | export const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => { 12 | if (isErrorWithMessage(maybeError)) return maybeError; 13 | 14 | try { 15 | return new Error(JSON.parse(JSON.stringify(maybeError))); 16 | } catch { 17 | // fallback in case there's an error stringifying the maybeError 18 | // like with circular references for example. 19 | return new Error(String(maybeError)); 20 | } 21 | }; 22 | 23 | export const getErrorMessage = (error: unknown): string => 24 | toErrorWithMessage(error)["message"]; 25 | -------------------------------------------------------------------------------- /api-docs/variables/useFormFields.md: -------------------------------------------------------------------------------- 1 | [**use-mailchimp-form v3.1.3**](../README.md) 2 | 3 | --- 4 | 5 | [use-mailchimp-form](../globals.md) / useFormFields 6 | 7 | # Variable: useFormFields() 8 | 9 | > `const` **useFormFields**: \<`T`\>(`initialState`) => `object` 10 | 11 | Defined in: [index.ts:15](https://github.com/imgarylai/use-mailchimp-form/blob/586051af57e37bb69b621a3d3314d1f3b5b97eea/src/index.ts#L15) 12 | 13 | ## Type Parameters 14 | 15 | ### T 16 | 17 | `T` _extends_ [`Params`](../interfaces/Params.md) 18 | 19 | ## Parameters 20 | 21 | ### initialState 22 | 23 | `T` 24 | 25 | ## Returns 26 | 27 | `object` 28 | 29 | ### fields 30 | 31 | > **fields**: `T` 32 | 33 | ### handleFieldChange() 34 | 35 | > **handleFieldChange**: (`event`) => `void` 36 | 37 | #### Parameters 38 | 39 | ##### event 40 | 41 | `ChangeEvent`\<`HTMLInputElement`\> 42 | 43 | #### Returns 44 | 45 | `void` 46 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import dts from "vite-plugin-dts"; 3 | import { resolve } from "path"; 4 | import { fileURLToPath, URL } from "node:url"; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | dts({ 9 | include: ["src/**/*"], 10 | exclude: ["**/*.test.*", "**/*.spec.*"], 11 | }), 12 | ], 13 | build: { 14 | lib: { 15 | entry: resolve( 16 | fileURLToPath(new URL(".", import.meta.url)), 17 | "src/index.ts" 18 | ), 19 | name: "UseMailChimpForm", 20 | formats: ["es", "cjs"], 21 | fileName: format => `index.${format === "es" ? "js" : "cjs"}`, 22 | }, 23 | rollupOptions: { 24 | external: ["react", "react-dom"], 25 | output: { 26 | globals: { 27 | react: "React", 28 | "react-dom": "ReactDOM", 29 | }, 30 | }, 31 | }, 32 | sourcemap: true, 33 | minify: true, 34 | target: "es2020", 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | export default { 3 | preset: "ts-jest", 4 | testEnvironment: "jsdom", 5 | collectCoverageFrom: [ 6 | "src/**/*.{ts,tsx}", 7 | "!src/**/*.d.ts", 8 | "!src/**/*.test.{ts,tsx}", 9 | "!src/**/__tests__/**", 10 | ], 11 | coverageThreshold: { 12 | global: { 13 | branches: 80, 14 | functions: 80, 15 | lines: 80, 16 | statements: 80, 17 | }, 18 | }, 19 | coverageReporters: ["json", "lcov", "text", "clover"], 20 | testMatch: [ 21 | "/src/**/*.test.{ts,tsx}", 22 | "/tests/**/*.test.{ts,tsx}", 23 | ], 24 | verbose: true, 25 | extensionsToTreatAsEsm: [".ts", ".tsx"], 26 | moduleNameMapper: { 27 | "^(\\.{1,2}/.*)\\.js$": "$1", 28 | }, 29 | transform: { 30 | "^.+\\.[tj]sx?$": ["ts-jest", { useESM: true }], 31 | }, 32 | transformIgnorePatterns: [ 33 | "node_modules/(?!(query-string|decode-uri-component|filter-obj|split-on-first)/)", 34 | ], 35 | }; 36 | -------------------------------------------------------------------------------- /api-docs/variables/useMailChimpForm.md: -------------------------------------------------------------------------------- 1 | [**use-mailchimp-form v3.1.3**](../README.md) 2 | 3 | --- 4 | 5 | [use-mailchimp-form](../globals.md) / useMailChimpForm 6 | 7 | # Variable: useMailChimpForm() 8 | 9 | > `const` **useMailChimpForm**: (`url`) => `object` 10 | 11 | Defined in: [index.ts:31](https://github.com/imgarylai/use-mailchimp-form/blob/586051af57e37bb69b621a3d3314d1f3b5b97eea/src/index.ts#L31) 12 | 13 | ## Parameters 14 | 15 | ### url 16 | 17 | `string` 18 | 19 | ## Returns 20 | 21 | `object` 22 | 23 | ### error 24 | 25 | > **error**: `boolean` 26 | 27 | ### handleSubmit() 28 | 29 | > **handleSubmit**: (`params`) => `void` 30 | 31 | #### Parameters 32 | 33 | ##### params 34 | 35 | [`Params`](../interfaces/Params.md) 36 | 37 | #### Returns 38 | 39 | `void` 40 | 41 | ### loading 42 | 43 | > **loading**: `boolean` 44 | 45 | ### message 46 | 47 | > **message**: `string` 48 | 49 | ### reset() 50 | 51 | > **reset**: () => `void` 52 | 53 | #### Returns 54 | 55 | `void` 56 | 57 | ### success 58 | 59 | > **success**: `boolean` 60 | -------------------------------------------------------------------------------- /tests/errors.test.ts: -------------------------------------------------------------------------------- 1 | import { getErrorMessage } from "../src/errors"; 2 | 3 | describe("Error messages", () => { 4 | test("should get error message from error object", () => { 5 | const error = new Error("Test error"); 6 | expect(getErrorMessage(error)).toEqual(error.message); 7 | }); 8 | 9 | test("should get error message from error json object", () => { 10 | const errorJson = { message: "Test Error" }; 11 | expect(getErrorMessage(errorJson)).toEqual(errorJson.message); 12 | }); 13 | 14 | test("should return error message from error string", () => { 15 | const errorString = "Test Error"; 16 | expect(getErrorMessage(errorString)).toEqual(errorString); 17 | }); 18 | 19 | test("should return error message if error message has cyclic object value", () => { 20 | const jsonMock = jest.spyOn(JSON, "stringify"); 21 | jsonMock.mockImplementation(() => { 22 | throw new TypeError("cyclic object value"); 23 | }); 24 | const errorString = "Test Error"; 25 | expect(getErrorMessage(errorString)).toEqual("Test Error"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gary Lai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Run testing 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest] 11 | node-version: [20.x, 22.x, 24.x] 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v6 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Setup Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v6 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | cache: "npm" 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | - name: Type check 28 | run: npm run type-check 29 | 30 | - name: Lint 31 | run: npm run lint 32 | 33 | - name: Test 34 | run: npm test 35 | 36 | - name: Test with coverage 37 | run: npm run test:coverage 38 | 39 | - name: Generate documentation 40 | run: npm run docs 41 | 42 | - name: Upload code coverage to codecov 43 | uses: codecov/codecov-action@v5 44 | with: 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | fail_ci_if_error: true 47 | -------------------------------------------------------------------------------- /tests/useMailChimpForm/handleSubmitFetchJsonpReject.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react"; 2 | import fetchJsonp from "fetch-jsonp"; 3 | import { useMailChimpForm } from "../../src"; 4 | import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const"; 5 | 6 | jest.mock("fetch-jsonp", () => 7 | jest 8 | .fn() 9 | .mockImplementation(() => Promise.reject(new Error("TESTING Message"))) 10 | ); 11 | 12 | describe("handleSubmit fetchJsonp reject", () => { 13 | beforeEach(() => { 14 | jest.resetModules(); 15 | }); 16 | 17 | test("should receive failure status and message", async () => { 18 | const { result } = renderHook(() => useMailChimpForm(TEST_MAILCHIMP_URL)); 19 | 20 | // Submit the form 21 | await act(async () => { 22 | await result.current.handleSubmit({ email: TEST_EMAIL }); 23 | }); 24 | 25 | // Verify the error state 26 | expect(fetchJsonp).toHaveBeenCalledWith( 27 | expect.stringContaining("post-json"), 28 | expect.any(Object) 29 | ); 30 | 31 | expect(result.current.loading).toBe(false); 32 | expect(result.current.success).toBe(false); 33 | expect(result.current.error).toBe(true); 34 | expect(result.current.message).toBe(TEST_MESSAGE); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/useMailChimpForm/handleSubmitFetchJsonpException.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react"; 2 | import fetchJsonp from "fetch-jsonp"; 3 | import { useMailChimpForm } from "../../src"; 4 | import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const"; 5 | 6 | jest.mock("fetch-jsonp", () => 7 | jest.fn().mockImplementation(() => { 8 | throw new Error("TESTING Message"); 9 | }) 10 | ); 11 | 12 | describe("handleSubmit fetchJsonp throws exception", () => { 13 | beforeEach(() => { 14 | jest.resetModules(); 15 | }); 16 | 17 | test("should receive failure status and message", async () => { 18 | const { result } = renderHook(() => useMailChimpForm(TEST_MAILCHIMP_URL)); 19 | 20 | // Submit the form 21 | await act(async () => { 22 | await result.current.handleSubmit({ email: TEST_EMAIL }); 23 | }); 24 | 25 | // Verify the error state 26 | expect(fetchJsonp).toHaveBeenCalledWith( 27 | expect.stringContaining("post-json"), 28 | expect.any(Object) 29 | ); 30 | 31 | expect(result.current.loading).toBe(false); 32 | expect(result.current.success).toBe(false); 33 | expect(result.current.error).toBe(true); 34 | expect(result.current.message).toBe(TEST_MESSAGE); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["ESNext"], 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "declarationMap": true, 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "removeComments": true, 12 | "importHelpers": true, 13 | "isolatedModules": true, 14 | "allowSyntheticDefaultImports": true, 15 | "esModuleInterop": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "strict": true, 18 | "noImplicitAny": true, 19 | "strictNullChecks": true, 20 | "strictFunctionTypes": true, 21 | "strictBindCallApply": true, 22 | "strictPropertyInitialization": true, 23 | "noImplicitThis": true, 24 | "useUnknownInCatchVariables": true, 25 | "alwaysStrict": true, 26 | "noUnusedLocals": true, 27 | "noUnusedParameters": true, 28 | "exactOptionalPropertyTypes": true, 29 | "noImplicitReturns": true, 30 | "noFallthroughCasesInSwitch": true, 31 | "noUncheckedIndexedAccess": true, 32 | "noImplicitOverride": true, 33 | "noPropertyAccessFromIndexSignature": true, 34 | "skipLibCheck": true 35 | }, 36 | "include": ["src/**/*", "tests/**/*", "vite.config.ts"], 37 | "exclude": ["node_modules", "dist"] 38 | } 39 | -------------------------------------------------------------------------------- /tests/useMailChimpForm/handleSubmitFail.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react"; 2 | import fetchJsonp from "fetch-jsonp"; 3 | import { useMailChimpForm } from "../../src"; 4 | import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const"; 5 | 6 | jest.mock("fetch-jsonp", () => 7 | jest.fn().mockImplementation(() => 8 | Promise.resolve({ 9 | ok: false, 10 | json: () => Promise.resolve({ msg: "TESTING Message" }), 11 | }) 12 | ) 13 | ); 14 | 15 | describe("handleSubmit mailchimp response shows not ok", () => { 16 | beforeEach(() => { 17 | jest.resetModules(); 18 | }); 19 | 20 | test("should receive failure status and message", async () => { 21 | const { result } = renderHook(() => useMailChimpForm(TEST_MAILCHIMP_URL)); 22 | 23 | // Submit the form 24 | await act(async () => { 25 | await result.current.handleSubmit({ email: TEST_EMAIL }); 26 | }); 27 | 28 | // Verify the failure state 29 | expect(fetchJsonp).toHaveBeenCalledWith( 30 | expect.stringContaining("post-json"), 31 | expect.any(Object) 32 | ); 33 | 34 | expect(result.current.loading).toBe(false); 35 | expect(result.current.success).toBe(false); 36 | expect(result.current.error).toBe(true); 37 | expect(result.current.message).toBe(TEST_MESSAGE); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /.github/workflows/npm-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | issues: write 15 | pull-requests: write 16 | id-token: write # Required for npm trusted publisher 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v6 20 | with: 21 | fetch-depth: 0 22 | persist-credentials: false 23 | 24 | - name: Setup Node.js 25 | uses: actions/setup-node@v6 26 | with: 27 | node-version: "24.x" 28 | cache: "npm" 29 | registry-url: 'https://registry.npmjs.org' 30 | 31 | - name: Update npm 32 | run: npm install -g npm@latest 33 | 34 | - name: Install dependencies 35 | run: npm ci 36 | 37 | - name: Run tests 38 | run: npm test 39 | 40 | - name: Run type check 41 | run: npm run type-check 42 | 43 | - name: Run lint 44 | run: npm run lint 45 | 46 | - name: Build 47 | run: npm run build 48 | 49 | - name: Release 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | run: npx semantic-release 54 | -------------------------------------------------------------------------------- /tests/useMailChimpForm/renderHooks.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react"; 2 | import { useFormFields, useMailChimpForm } from "../../src"; 3 | import { TEST_EMAIL, TEST_MAILCHIMP_URL } from "../const"; 4 | import React from "react"; 5 | 6 | describe("useFormFields", () => { 7 | test("should return initial form state", () => { 8 | const { result } = renderHook(() => useFormFields({ email: "" })); 9 | expect(result.current.fields).toEqual({ email: "" }); 10 | }); 11 | 12 | test("should update field value", () => { 13 | const { result } = renderHook(() => useFormFields({ email: "" })); 14 | 15 | const event = { 16 | target: { id: "email", value: TEST_EMAIL }, 17 | } as unknown as React.ChangeEvent; 18 | 19 | act(() => { 20 | result.current.handleFieldChange(event); 21 | }); 22 | 23 | expect(result.current.fields).toEqual({ email: TEST_EMAIL }); 24 | }); 25 | }); 26 | 27 | describe("useMailChimpForm", () => { 28 | test("should return initial form status", () => { 29 | const { result } = renderHook(() => useMailChimpForm(TEST_MAILCHIMP_URL)); 30 | 31 | // Only check the properties we care about 32 | expect(result.current.success).toBe(false); 33 | expect(result.current.error).toBe(false); 34 | expect(result.current.loading).toBe(false); 35 | expect(result.current.message).toBe(""); 36 | expect(typeof result.current.handleSubmit).toBe("function"); 37 | expect(typeof result.current.reset).toBe("function"); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/useMailChimpForm/handleSubmitSuccess.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react"; 2 | import fetchJsonp from "fetch-jsonp"; 3 | import { useMailChimpForm } from "../../src"; 4 | import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const"; 5 | 6 | jest.mock("fetch-jsonp", () => 7 | jest.fn().mockImplementation(() => 8 | Promise.resolve({ 9 | ok: true, 10 | json: () => Promise.resolve({ msg: "TESTING Message" }), 11 | }) 12 | ) 13 | ); 14 | 15 | describe("handleSubmit user subscribes successfully", () => { 16 | beforeEach(() => { 17 | jest.resetModules(); 18 | }); 19 | 20 | test("should receive success status and message", async () => { 21 | const { result } = renderHook(() => useMailChimpForm(TEST_MAILCHIMP_URL)); 22 | 23 | // Submit the form 24 | await act(async () => { 25 | await result.current.handleSubmit({ email: TEST_EMAIL }); 26 | }); 27 | 28 | // Verify the success state 29 | expect(fetchJsonp).toHaveBeenCalledWith( 30 | expect.stringContaining("post-json"), 31 | expect.any(Object) 32 | ); 33 | 34 | expect(result.current.loading).toBe(false); 35 | expect(result.current.success).toBe(true); 36 | expect(result.current.error).toBe(false); 37 | expect(result.current.message).toBe(TEST_MESSAGE); 38 | 39 | // Test reset functionality 40 | act(() => { 41 | result.current.reset(); 42 | }); 43 | 44 | // Verify reset state 45 | expect(result.current.loading).toBe(false); 46 | expect(result.current.success).toBe(false); 47 | expect(result.current.error).toBe(false); 48 | expect(result.current.message).toBe(""); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fetchJsonp from "fetch-jsonp"; 2 | import queryString from "query-string"; 3 | import { ChangeEvent, useState } from "react"; 4 | import { getErrorMessage } from "./errors"; 5 | 6 | export interface Params { 7 | [key: string]: unknown; 8 | } 9 | 10 | interface Response { 11 | result: string; 12 | msg: string; 13 | } 14 | 15 | export const useFormFields: ( 16 | initialState: T 17 | ) => { 18 | handleFieldChange: (event: ChangeEvent) => void; 19 | fields: T; 20 | } = (initialState: T) => { 21 | const [fields, setValues] = useState(initialState); 22 | const handleFieldChange = (event: ChangeEvent): void => { 23 | setValues({ 24 | ...fields, 25 | [event.target.id]: event.target.value, 26 | }); 27 | }; 28 | return { fields, handleFieldChange }; 29 | }; 30 | 31 | export const useMailChimpForm: (url: string) => { 32 | handleSubmit: (params: Params) => void; 33 | success: boolean; 34 | reset: () => void; 35 | loading: boolean; 36 | error: boolean; 37 | message: string; 38 | } = (url: string) => { 39 | const initStatusState = { 40 | loading: false, 41 | error: false, 42 | success: false, 43 | }; 44 | const [status, setStatus] = useState(initStatusState); 45 | const [message, setMessage] = useState(""); 46 | 47 | const handleSubmit = async (params: Params): Promise => { 48 | const query = queryString.stringify(params); 49 | const endpoint = url.replace("/post?", "/post-json?") + "&" + query; 50 | setStatus({ ...status, loading: true }); 51 | setMessage(""); 52 | try { 53 | const response: fetchJsonp.Response = await fetchJsonp(endpoint, { 54 | jsonpCallback: "c", 55 | }); 56 | 57 | const data: Response = await response.json(); 58 | 59 | const error = !response.ok || data.result === "error"; 60 | 61 | setStatus({ 62 | ...initStatusState, 63 | success: !error, 64 | error, 65 | }); 66 | 67 | setMessage(data.msg); 68 | } catch (error) { 69 | setStatus({ ...initStatusState, error: true }); 70 | setMessage(getErrorMessage(error)); 71 | } 72 | }; 73 | 74 | const reset: () => void = () => { 75 | setMessage(""); 76 | setStatus(initStatusState); 77 | }; 78 | 79 | return { 80 | status: status, 81 | loading: status.loading, 82 | success: status.success, 83 | error: status.error, 84 | message, 85 | handleSubmit, 86 | reset, 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import tsPlugin from "@typescript-eslint/eslint-plugin"; 3 | import tsParser from "@typescript-eslint/parser"; 4 | import eslintConfigPrettier from "eslint-config-prettier"; 5 | import reactPlugin from "eslint-plugin-react"; 6 | import reactHooksPlugin from "eslint-plugin-react-hooks"; 7 | import prettier from "eslint-plugin-prettier"; 8 | 9 | export default [ 10 | { 11 | ignores: [ 12 | "dist/**", 13 | "node_modules/**", 14 | "coverage/**", 15 | ".eslintrc.js", 16 | ".commitlintrc.js", 17 | ".releaserc.js", 18 | "babel.config.js", 19 | "jest.config.js", 20 | ], 21 | }, 22 | { 23 | files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], 24 | ...eslint.configs.recommended, 25 | }, 26 | { 27 | files: ["**/*.{ts,tsx,mtsx}"], 28 | plugins: { 29 | "@typescript-eslint": tsPlugin, 30 | react: reactPlugin, 31 | "react-hooks": reactHooksPlugin, 32 | prettier: prettier, 33 | }, 34 | languageOptions: { 35 | parser: tsParser, 36 | parserOptions: { 37 | project: ["./tsconfig.json", "./tsconfig.build.json"], 38 | ecmaVersion: 2020, 39 | sourceType: "module", 40 | ecmaFeatures: { 41 | jsx: true, 42 | }, 43 | }, 44 | globals: { 45 | jest: true, 46 | describe: true, 47 | it: true, 48 | expect: true, 49 | test: true, 50 | beforeEach: true, 51 | afterEach: true, 52 | beforeAll: true, 53 | afterAll: true, 54 | HTMLInputElement: true, 55 | window: true, 56 | document: true, 57 | navigator: true, 58 | console: true, 59 | setTimeout: true, 60 | clearTimeout: true, 61 | setInterval: true, 62 | clearInterval: true, 63 | Promise: true, 64 | Error: true, 65 | TypeError: true, 66 | JSON: true, 67 | }, 68 | }, 69 | rules: { 70 | ...tsPlugin.configs.recommended.rules, 71 | ...reactPlugin.configs.recommended.rules, 72 | ...reactHooksPlugin.configs.recommended.rules, 73 | "prettier/prettier": ["error", {}, { usePrettierrc: true }], 74 | "@typescript-eslint/explicit-function-return-type": "warn", 75 | "@typescript-eslint/no-unused-vars": [ 76 | "error", 77 | { argsIgnorePattern: "^_" }, 78 | ], 79 | "@typescript-eslint/no-explicit-any": "warn", 80 | "no-console": ["warn", { allow: ["warn", "error"] }], 81 | }, 82 | settings: { 83 | react: { 84 | version: "detect", 85 | }, 86 | }, 87 | }, 88 | { 89 | files: ["tests/**/*.{ts,tsx}"], 90 | plugins: { 91 | "@typescript-eslint": tsPlugin, 92 | }, 93 | languageOptions: { 94 | parser: tsParser, 95 | parserOptions: { 96 | project: "./tsconfig.json", 97 | }, 98 | globals: { 99 | jest: true, 100 | describe: true, 101 | it: true, 102 | expect: true, 103 | test: true, 104 | beforeEach: true, 105 | afterEach: true, 106 | beforeAll: true, 107 | afterAll: true, 108 | HTMLInputElement: true, 109 | window: true, 110 | document: true, 111 | navigator: true, 112 | console: true, 113 | setTimeout: true, 114 | clearTimeout: true, 115 | setInterval: true, 116 | clearInterval: true, 117 | Promise: true, 118 | Error: true, 119 | TypeError: true, 120 | JSON: true, 121 | }, 122 | }, 123 | rules: { 124 | ...tsPlugin.configs.recommended.rules, 125 | "@typescript-eslint/explicit-function-return-type": "off", 126 | "@typescript-eslint/no-explicit-any": "off", 127 | "@typescript-eslint/no-non-null-assertion": "off", 128 | }, 129 | }, 130 | eslintConfigPrettier, 131 | ]; 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": false, 3 | "name": "use-mailchimp-form", 4 | "version": "3.2.0", 5 | "description": "MailChimp Form implemented in React Hooks", 6 | "license": "MIT", 7 | "author": "Gary Lai ", 8 | "homepage": "https://github.com/imgarylai/use-mailchimp-form#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/imgarylai/use-mailchimp-form.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/imgarylai/use-mailchimp-form/issues" 15 | }, 16 | "type": "module", 17 | "exports": { 18 | "types": "./dist/index.d.ts", 19 | "import": "./dist/index.js", 20 | "require": "./dist/index.cjs" 21 | }, 22 | "main": "./dist/index.cjs", 23 | "module": "./dist/index.js", 24 | "files": [ 25 | "dist", 26 | "src" 27 | ], 28 | "scripts": { 29 | "build": "vite build", 30 | "clean": "rimraf dist coverage", 31 | "dev": "vite build --watch", 32 | "predocs": "rimraf api-docs", 33 | "docs": "typedoc", 34 | "docs:watch": "typedoc --watch", 35 | "lint": "eslint .", 36 | "prepack": "npm run build", 37 | "prepare": "husky", 38 | "test": "jest", 39 | "test:coverage": "jest --coverage", 40 | "type-check": "tsc --noEmit" 41 | }, 42 | "config": { 43 | "commitizen": { 44 | "path": "./node_modules/cz-conventional-changelog" 45 | } 46 | }, 47 | "sideEffects": false, 48 | "types": "./dist/index.d.ts", 49 | "dependencies": { 50 | "fetch-jsonp": "1.3.0", 51 | "query-string": "^9.3.1" 52 | }, 53 | "peerDependencies": { 54 | "react": ">= 18.2.0" 55 | }, 56 | "devDependencies": { 57 | "@commitlint/cli": "20.2.0", 58 | "@commitlint/config-conventional": "20.2.0", 59 | "@eslint/js": "^9.0.0", 60 | "@semantic-release/changelog": "^6.0.0", 61 | "@semantic-release/commit-analyzer": "^13.0.0", 62 | "@semantic-release/git": "^10.0.0", 63 | "@semantic-release/github": "12.0.2", 64 | "@semantic-release/npm": "^13.0.0", 65 | "@semantic-release/release-notes-generator": "^14.0.0", 66 | "@testing-library/react": "16.3.1", 67 | "@testing-library/user-event": "14.6.1", 68 | "@types/jest": "30.0.0", 69 | "@types/jsdom": "27.0.0", 70 | "@types/node": "^24.0.0", 71 | "@types/react": "19.2.7", 72 | "@typescript-eslint/eslint-plugin": "^8.0.0", 73 | "@typescript-eslint/parser": "^8.0.0", 74 | "cz-conventional-changelog": "3.3.0", 75 | "eslint": "^9.0.0", 76 | "eslint-config-prettier": "^10.0.0", 77 | "eslint-plugin-prettier": "5.5.4", 78 | "eslint-plugin-react": "^7.34.0", 79 | "eslint-plugin-react-hooks": "^7.0.0", 80 | "husky": "9.1.7", 81 | "jest": "^30.0.0", 82 | "jest-environment-jsdom": "^30.0.0", 83 | "jsdom": "27.3.0", 84 | "lint-staged": "16.2.7", 85 | "prettier": "3.7.4", 86 | "prettier-package-json": "^2.4.11", 87 | "react": "19.2.3", 88 | "react-dom": "19.2.3", 89 | "rimraf": "^6.0.0", 90 | "semantic-release": "^25.0.0", 91 | "sort-package-json": "3.6.0", 92 | "ts-jest": "^29.1.2", 93 | "typedoc": "^0.28.5", 94 | "typedoc-plugin-markdown": "4.9.0", 95 | "typescript": "5.9.3", 96 | "vite": "^7.0.0", 97 | "vite-plugin-dts": "^4.0.0" 98 | }, 99 | "keywords": [ 100 | "hooks", 101 | "mailchimp", 102 | "react" 103 | ], 104 | "engines": { 105 | "node": ">=20.0.0", 106 | "npm": ">=10.0.0" 107 | }, 108 | "publishConfig": { 109 | "access": "public" 110 | }, 111 | "lint-staged": { 112 | "src/**/*.{ts,tsx}": [ 113 | "eslint --fix", 114 | "prettier --write", 115 | "jest --bail --findRelatedTests" 116 | ], 117 | "tests/**/*.{ts,tsx}": [ 118 | "eslint --fix", 119 | "prettier --write", 120 | "jest --bail --findRelatedTests" 121 | ], 122 | "*.{js,jsx}": [ 123 | "eslint --fix", 124 | "prettier --write" 125 | ], 126 | "*.config.{js,ts}": [ 127 | "eslint --fix", 128 | "prettier --write" 129 | ], 130 | "*.{json,md}": [ 131 | "prettier --write" 132 | ], 133 | "package.json": [ 134 | "prettier-package-json --write" 135 | ] 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # use-mailchimp-form [![npm](https://img.shields.io/npm/v/use-mailchimp-form)](https://www.npmjs.com/package/use-mailchimp-form) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![codecov](https://codecov.io/gh/imgarylai/use-mailchimp-form/branch/main/graph/badge.svg?token=HK9ISNOG26)](https://codecov.io/gh/imgarylai/use-mailchimp-form) 2 | 3 | A React hooks-based solution for integrating [MailChimp](https://mailchimp.com/) subscribe forms into your React applications. This package handles all the business logic, allowing you to focus on the UI implementation. The view component can be fully customized or implemented with any React form library. 4 | 5 | ## Features 6 | 7 | - 🎣 React Hooks-based implementation 8 | - 🎨 Fully customizable UI 9 | - 📦 Zero dependencies (except React) 10 | - 🔒 Type-safe with TypeScript support 11 | - 🚀 Modern ESM and CommonJS support 12 | 13 | ## Requirements 14 | 15 | - Node.js >= 20.0.0 16 | - npm >= 10.0.0 17 | - React >= 18.2.0 18 | 19 | ## Installation 20 | 21 | Using npm: 22 | 23 | ```bash 24 | npm install use-mailchimp-form 25 | ``` 26 | 27 | Using yarn: 28 | 29 | ```bash 30 | yarn add use-mailchimp-form 31 | ``` 32 | 33 | ## Setup 34 | 35 | ### Getting Your Mailchimp Form Endpoint 36 | 37 | 1. Navigate to the `Audience` page in your Mailchimp dashboard 38 | 2. Click the dropdown menu `Manage Audience > Signup Forms` 39 | 3. Select `Embedded Form` 40 | 4. In the generated code, locate the form's action URL. It will look like: 41 | ``` 42 | https://aaaaaaaaa.us20.list-manage.com/subscribe/post?u=xxxxxxxxxxxxxxxxxx&id=yyyyyyyyyy 43 | ``` 44 | 45 | ## Usage 46 | 47 | ### Basic Example 48 | 49 | ```jsx 50 | import { useFormFields, useMailChimpForm } from "use-mailchimp-form"; 51 | 52 | export default function SubscribeForm() { 53 | const url = "YOUR_SUBSCRIBE_URL"; 54 | const { loading, error, success, message, handleSubmit } = 55 | useMailChimpForm(url); 56 | const { fields, handleFieldChange } = useFormFields({ 57 | EMAIL: "", 58 | }); 59 | 60 | return ( 61 |
62 |
{ 64 | event.preventDefault(); 65 | handleSubmit(fields); 66 | }} 67 | > 68 | 76 | 79 |
80 | 81 | {loading &&

Submitting...

} 82 | {error &&

{message}

} 83 | {success &&

{message}

} 84 |
85 | ); 86 | } 87 | ``` 88 | 89 | ### Hook Return Values 90 | 91 | #### useMailChimpForm 92 | 93 | - `loading`: Boolean indicating submission status 94 | - `error`: Boolean indicating if an error occurred 95 | - `success`: Boolean indicating successful submission 96 | - `message`: String containing response message 97 | - `handleSubmit`: Function to handle form submission 98 | 99 | #### useFormFields 100 | 101 | - `fields`: Object containing form field values 102 | - `handleFieldChange`: Function to handle field changes 103 | 104 | ## Custom Form Integration 105 | 106 | The `useFormFields` hook is optional. You can use your preferred form management solution (Formik, React Hook Form, etc.) with `useMailChimpForm`: 107 | 108 | ```jsx 109 | import { useMailChimpForm } from "use-mailchimp-form"; 110 | import { useForm } from "react-hook-form"; 111 | 112 | export default function CustomForm() { 113 | const { handleSubmit: submitToMailchimp } = useMailChimpForm("YOUR_URL"); 114 | const { register, handleSubmit } = useForm(); 115 | 116 | const onSubmit = data => { 117 | submitToMailchimp(data); 118 | }; 119 | 120 | return ( 121 |
122 | 123 | 124 |
125 | ); 126 | } 127 | ``` 128 | 129 | ## Contributing 130 | 131 | Contributions are welcome! Please feel free to submit a Pull Request. 132 | 133 | ## License 134 | 135 | MIT © [Gary Lai](https://github.com/imgarylai) 136 | -------------------------------------------------------------------------------- /api-docs/README.md: -------------------------------------------------------------------------------- 1 | **use-mailchimp-form v3.1.3** 2 | 3 | --- 4 | 5 | # use-mailchimp-form [![npm](https://img.shields.io/npm/v/use-mailchimp-form)](https://www.npmjs.com/package/use-mailchimp-form) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![codecov](https://codecov.io/gh/imgarylai/use-mailchimp-form/branch/main/graph/badge.svg?token=HK9ISNOG26)](https://codecov.io/gh/imgarylai/use-mailchimp-form) 6 | 7 | A React hooks-based solution for integrating [MailChimp](https://mailchimp.com/) subscribe forms into your React applications. This package handles all the business logic, allowing you to focus on the UI implementation. The view component can be fully customized or implemented with any React form library. 8 | 9 | ## Features 10 | 11 | - 🎣 React Hooks-based implementation 12 | - 🎨 Fully customizable UI 13 | - 📦 Zero dependencies (except React) 14 | - 🔒 Type-safe with TypeScript support 15 | - 🚀 Modern ESM and CommonJS support 16 | 17 | ## Requirements 18 | 19 | - Node.js >= 20.0.0 20 | - npm >= 10.0.0 21 | - React >= 18.2.0 22 | 23 | ## Installation 24 | 25 | Using npm: 26 | 27 | ```bash 28 | npm install use-mailchimp-form 29 | ``` 30 | 31 | Using yarn: 32 | 33 | ```bash 34 | yarn add use-mailchimp-form 35 | ``` 36 | 37 | ## Setup 38 | 39 | ### Getting Your Mailchimp Form Endpoint 40 | 41 | 1. Navigate to the `Audience` page in your Mailchimp dashboard 42 | 2. Click the dropdown menu `Manage Audience > Signup Forms` 43 | 3. Select `Embedded Form` 44 | 4. In the generated code, locate the form's action URL. It will look like: 45 | ``` 46 | https://aaaaaaaaa.us20.list-manage.com/subscribe/post?u=xxxxxxxxxxxxxxxxxx&id=yyyyyyyyyy 47 | ``` 48 | 49 | ## Usage 50 | 51 | ### Basic Example 52 | 53 | ```jsx 54 | import { useFormFields, useMailChimpForm } from "use-mailchimp-form"; 55 | 56 | export default function SubscribeForm() { 57 | const url = "YOUR_SUBSCRIBE_URL"; 58 | const { loading, error, success, message, handleSubmit } = 59 | useMailChimpForm(url); 60 | const { fields, handleFieldChange } = useFormFields({ 61 | EMAIL: "", 62 | }); 63 | 64 | return ( 65 |
66 |
{ 68 | event.preventDefault(); 69 | handleSubmit(fields); 70 | }} 71 | > 72 | 80 | 83 |
84 | 85 | {loading &&

Submitting...

} 86 | {error &&

{message}

} 87 | {success &&

{message}

} 88 |
89 | ); 90 | } 91 | ``` 92 | 93 | ### Hook Return Values 94 | 95 | #### useMailChimpForm 96 | 97 | - `loading`: Boolean indicating submission status 98 | - `error`: Boolean indicating if an error occurred 99 | - `success`: Boolean indicating successful submission 100 | - `message`: String containing response message 101 | - `handleSubmit`: Function to handle form submission 102 | 103 | #### useFormFields 104 | 105 | - `fields`: Object containing form field values 106 | - `handleFieldChange`: Function to handle field changes 107 | 108 | ## Custom Form Integration 109 | 110 | The `useFormFields` hook is optional. You can use your preferred form management solution (Formik, React Hook Form, etc.) with `useMailChimpForm`: 111 | 112 | ```jsx 113 | import { useMailChimpForm } from "use-mailchimp-form"; 114 | import { useForm } from "react-hook-form"; 115 | 116 | export default function CustomForm() { 117 | const { handleSubmit: submitToMailchimp } = useMailChimpForm("YOUR_URL"); 118 | const { register, handleSubmit } = useForm(); 119 | 120 | const onSubmit = data => { 121 | submitToMailchimp(data); 122 | }; 123 | 124 | return ( 125 |
126 | 127 | 128 |
129 | ); 130 | } 131 | ``` 132 | 133 | ## Contributing 134 | 135 | Contributions are welcome! Please feel free to submit a Pull Request. 136 | 137 | ## License 138 | 139 | MIT © [Gary Lai](https://github.com/imgarylai) 140 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # Generated files 14 | .idea/**/contentModel.xml 15 | 16 | # Sensitive or high-churn files 17 | .idea/**/dataSources/ 18 | .idea/**/dataSources.ids 19 | .idea/**/dataSources.local.xml 20 | .idea/**/sqlDataSources.xml 21 | .idea/**/dynamic.xml 22 | .idea/**/uiDesigner.xml 23 | .idea/**/dbnavigator.xml 24 | 25 | # Gradle 26 | .idea/**/gradle.xml 27 | .idea/**/libraries 28 | 29 | # Gradle and Maven with auto-import 30 | # When using Gradle or Maven with auto-import, you should exclude module files, 31 | # since they will be recreated, and may cause churn. Uncomment if using 32 | # auto-import. 33 | # .idea/artifacts 34 | # .idea/compiler.xml 35 | # .idea/jarRepositories.xml 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | # *.iml 40 | # *.ipr 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | ### Node template 76 | # Logs 77 | logs 78 | *.log 79 | npm-debug.log* 80 | yarn-debug.log* 81 | yarn-error.log* 82 | lerna-debug.log* 83 | 84 | # Diagnostic reports (https://nodejs.org/api/report.html) 85 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 86 | 87 | # Runtime data 88 | pids 89 | *.pid 90 | *.seed 91 | *.pid.lock 92 | 93 | # Directory for instrumented libs generated by jscoverage/JSCover 94 | lib-cov 95 | 96 | # Coverage directory used by tools like istanbul 97 | coverage 98 | *.lcov 99 | 100 | # nyc test coverage 101 | .nyc_output 102 | 103 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 104 | .grunt 105 | 106 | # Bower dependency directory (https://bower.io/) 107 | bower_components 108 | 109 | # node-waf configuration 110 | .lock-wscript 111 | 112 | # Compiled binary addons (https://nodejs.org/api/addons.html) 113 | build/Release 114 | 115 | # Dependency directories 116 | node_modules/ 117 | jspm_packages/ 118 | 119 | # Snowpack dependency directory (https://snowpack.dev/) 120 | web_modules/ 121 | 122 | # TypeScript cache 123 | *.tsbuildinfo 124 | 125 | # Optional npm cache directory 126 | .npm 127 | 128 | # Optional eslint cache 129 | .eslintcache 130 | 131 | # Microbundle cache 132 | .rpt2_cache/ 133 | .rts2_cache_cjs/ 134 | .rts2_cache_es/ 135 | .rts2_cache_umd/ 136 | 137 | # Optional REPL history 138 | .node_repl_history 139 | 140 | # Output of 'npm pack' 141 | *.tgz 142 | 143 | # Yarn Integrity file 144 | .yarn-integrity 145 | 146 | # dotenv environment variables file 147 | .env 148 | .env.test 149 | 150 | # parcel-bundler cache (https://parceljs.org/) 151 | .cache 152 | .parcel-cache 153 | 154 | # Next.js build output 155 | .next 156 | 157 | # Nuxt.js build / generate output 158 | .nuxt 159 | dist 160 | 161 | # Gatsby files 162 | .cache/ 163 | # Comment in the public line in if your project uses Gatsby and not Next.js 164 | # https://nextjs.org/blog/next-9-1#public-directory-support 165 | # public 166 | 167 | # vuepress build output 168 | .vuepress/dist 169 | 170 | # Serverless directories 171 | .serverless/ 172 | 173 | # FuseBox cache 174 | .fusebox/ 175 | 176 | # DynamoDB Local files 177 | .dynamodb/ 178 | 179 | # TernJS port file 180 | .tern-port 181 | 182 | # Stores VSCode versions used for testing VSCode extensions 183 | .vscode-test 184 | 185 | # yarn v2 186 | 187 | .yarn/cache 188 | .yarn/unplugged 189 | .yarn/build-state.yml 190 | .pnp.* 191 | 192 | ### macOS template 193 | # General 194 | .DS_Store 195 | .AppleDouble 196 | .LSOverride 197 | 198 | # Icon must end with two \r 199 | Icon 200 | 201 | # Thumbnails 202 | ._* 203 | 204 | # Files that might appear in the root of a volume 205 | .DocumentRevisions-V100 206 | .fseventsd 207 | .Spotlight-V100 208 | .TemporaryItems 209 | .Trashes 210 | .VolumeIcon.icns 211 | .com.apple.timemachine.donotpresent 212 | 213 | # Directories potentially created on remote AFP share 214 | .AppleDB 215 | .AppleDesktop 216 | Network Trash Folder 217 | Temporary Items 218 | .apdisk 219 | 220 | ### VisualStudioCode template 221 | .vscode/ 222 | .vscode/* 223 | !.vscode/settings.json 224 | !.vscode/tasks.json 225 | !.vscode/launch.json 226 | !.vscode/extensions.json 227 | *.code-workspace 228 | 229 | # Local History for Visual Studio Code 230 | .history/ 231 | .idea 232 | 233 | mise.toml -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [3.2.0](https://github.com/imgarylai/use-mailchimp-form/compare/v3.1.5...v3.2.0) (2025-12-15) 2 | 3 | ### Features 4 | 5 | - migrate from tsup to vite for build tooling ([c549bf6](https://github.com/imgarylai/use-mailchimp-form/commit/c549bf6afa11f1d68c61f9713c3b11ab1302e62b)) 6 | 7 | ## [3.1.5](https://github.com/imgarylai/use-mailchimp-form/compare/v3.1.4...v3.1.5) (2025-12-14) 8 | 9 | ### Bug Fixes 10 | 11 | - trigger patch release for dependency updates ([bffbef5](https://github.com/imgarylai/use-mailchimp-form/commit/bffbef5f2a5b88e47be2d949a34bce95564001cf)) 12 | 13 | ## [3.1.4](https://github.com/imgarylai/use-mailchimp-form/compare/v3.1.3...v3.1.4) (2025-06-08) 14 | 15 | ### Bug Fixes 16 | 17 | - **typedoc:** use api-docs for typedoc directory ([ef6af14](https://github.com/imgarylai/use-mailchimp-form/commit/ef6af14e7bbb74130f26295fbbb2599ddee835d1)) 18 | 19 | ## [3.1.3](https://github.com/imgarylai/use-mailchimp-form/compare/v3.1.2...v3.1.3) (2025-04-13) 20 | 21 | ### Bug Fixes 22 | 23 | - **deps:** update dependency fetch-jsonp to v1.3.0 ([c7704f0](https://github.com/imgarylai/use-mailchimp-form/commit/c7704f0bb43d053f3300c7f4ed7ddb01c22c434b)) 24 | 25 | ## [3.1.2](https://github.com/imgarylai/use-mailchimp-form/compare/v3.1.1...v3.1.2) (2025-04-13) 26 | 27 | ### Bug Fixes 28 | 29 | - drop windows support ([68cf1dc](https://github.com/imgarylai/use-mailchimp-form/commit/68cf1dc8fc5b54d627bae68f5d0ab8f517583569)) 30 | 31 | ## [3.1.1](https://github.com/imgarylai/use-mailchimp-form/compare/v3.1.0...v3.1.1) (2025-04-13) 32 | 33 | ### Bug Fixes 34 | 35 | - update dependencies and configuration ([8b104a2](https://github.com/imgarylai/use-mailchimp-form/commit/8b104a2dd616eb3946052aab1d7e5de74ceccc5b)) 36 | 37 | # [3.1.0](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.6...v3.1.0) (2023-01-06) 38 | 39 | ### Features 40 | 41 | - **nodejs:** last version for supporting v12 nodejs ([313b8f2](https://github.com/imgarylai/use-mailchimp-form/commit/313b8f226641cfbcb976726010605adcb86e03fd)) 42 | 43 | ## [3.0.6](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.5...v3.0.6) (2022-12-02) 44 | 45 | ### Bug Fixes 46 | 47 | - **deps:** update dependency query-string to v7.1.3 ([a23f0e4](https://github.com/imgarylai/use-mailchimp-form/commit/a23f0e40d8cf0ab4a08133abe9a830617f98f96e)) 48 | 49 | ## [3.0.5](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.4...v3.0.5) (2022-12-01) 50 | 51 | ### Bug Fixes 52 | 53 | - **deps:** update dependency query-string to v7.1.2 ([d58a32a](https://github.com/imgarylai/use-mailchimp-form/commit/d58a32aec396096a271ea56ae0b8b80937de089c)) 54 | 55 | ## [3.0.4](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.3...v3.0.4) (2022-11-28) 56 | 57 | ### Bug Fixes 58 | 59 | - **index.ts:** handle mailchimp error response status ([6e53cd4](https://github.com/imgarylai/use-mailchimp-form/commit/6e53cd4212a3ecaa89565662d7b95aeb9155de55)) 60 | 61 | ## [3.0.3](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.2...v3.0.3) (2022-09-13) 62 | 63 | ### Bug Fixes 64 | 65 | - **deps:** update dependency fetch-jsonp to v1.2.3 ([1f072ff](https://github.com/imgarylai/use-mailchimp-form/commit/1f072ff480a05386f99b4d0270efbcdd38343c48)) 66 | 67 | ## [3.0.2](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.1...v3.0.2) (2022-07-21) 68 | 69 | ### Bug Fixes 70 | 71 | - **package:** resolve react version issue ([0ae74fa](https://github.com/imgarylai/use-mailchimp-form/commit/0ae74fa6f4c81f0264390929bdb8d838d0a8c459)) 72 | 73 | ## [3.0.1](https://github.com/imgarylai/use-mailchimp-form/compare/v3.0.0...v3.0.1) (2022-05-24) 74 | 75 | ### Bug Fixes 76 | 77 | - **package:** fix jest version issue ([2dc8cd8](https://github.com/imgarylai/use-mailchimp-form/commit/2dc8cd8925953f25c0aac116cfee7ba1bf99fa95)) 78 | 79 | # [3.0.0](https://github.com/imgarylai/use-mailchimp-form/compare/v2.0.5...v3.0.0) (2022-02-06) 80 | 81 | ### Code Refactoring 82 | 83 | - **package:** replace jsonp with fetch-jsonp ([50bc0b1](https://github.com/imgarylai/use-mailchimp-form/commit/50bc0b1d3476fcc5b330e7ba72ef40cd275dee94)) 84 | 85 | ### BREAKING CHANGES 86 | 87 | - **package:** jsonp dependency replacement 88 | 89 | ## [2.0.5](https://github.com/imgarylai/use-mailchimp-form/compare/v2.0.4...v2.0.5) (2022-02-05) 90 | 91 | ### Performance Improvements 92 | 93 | - **package:** update dependencies ([42ee97c](https://github.com/imgarylai/use-mailchimp-form/commit/42ee97c32c9c50e3d2aaf3522c4c859f05b63ea9)) 94 | 95 | ## [2.0.4](https://github.com/imgarylai/use-mailchimp-form/compare/v2.0.3...v2.0.4) (2021-09-21) 96 | 97 | ### Performance Improvements 98 | 99 | - **package:** update dependencies ([5df2e3f](https://github.com/imgarylai/use-mailchimp-form/commit/5df2e3fc8e1364f239a0b1014dc9ab377592a491)) 100 | 101 | ## [2.0.3](https://github.com/imgarylai/use-mailchimp-form/compare/v2.0.2...v2.0.3) (2021-07-09) 102 | 103 | ### Bug Fixes 104 | 105 | - **deps:** update dependency query-string to v7 ([4e3e1cd](https://github.com/imgarylai/use-mailchimp-form/commit/4e3e1cd0ed3c60709128c0ff91752a7765f1c245)) 106 | 107 | ## [2.0.2](https://github.com/imgarylai/use-mailchimp-form/compare/v2.0.1...v2.0.2) (2021-07-09) 108 | 109 | ### Bug Fixes 110 | 111 | - **readme:** typo. it should be fields instead of params ([ce25228](https://github.com/imgarylai/use-mailchimp-form/commit/ce2522883855afc65ff5518e0c76f6e3450a8227)), closes [#21](https://github.com/imgarylai/use-mailchimp-form/issues/21) 112 | --------------------------------------------------------------------------------