diff --git a/README.md b/README.md
index 1453acd..cc98645 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,9 @@
**react-component-form** is a lightweight form component for [React.js](https://reactjs.org/), it allows you to get the inputs values without state thanks to `onChange` or `onSubmit` props.
-Demo : [https://divlo.github.io/react-component-form/](https://divlo.github.io/react-component-form/).
+There is also a [React Hooks](https://reactjs.org/docs/hooks-intro.html) to be used in combination with the `
` component to validate the data with [Ajv JSON schema validator](https://ajv.js.org/), see [advanced usage](#%EF%B8%8F-advanced-usage).
+
+Demo: [https://divlo.github.io/react-component-form/](https://divlo.github.io/react-component-form/).
## 💾 Install
@@ -32,9 +34,12 @@ npm install --save react-component-form
## ⚙️ Usage
+_Note : The examples use TypeScript, but obviously you can use JavaScript. Be aware that `HandleForm` is the type definition for the `onChange` and `onSubmit` props._
+
```tsx
import React from 'react'
-import { Form, HandleForm } from 'react-component-form'
+import { Form } from 'react-component-form'
+import type { HandleForm } from 'react-component-form'
const Example = () => {
const handleSubmit: HandleForm = (formData, formElement) => {
@@ -51,15 +56,52 @@ const Example = () => {
}
```
-_Note : The example use TypeScript, but obviously you can use JavaScript. Be aware that `HandleForm` is the type definition for the `onChange` and `onSubmit` props._
-
Basically you have access to the same props of the HTML `form` tag in React, but the onSubmit and the onChange props are differents.
-Instead to get the `event` param you get `formData` and `formElement` params :
+Instead to get the `event` param you get `formData` and `formElement` parameters:
- `formData`: It's an object where the keys are the name of your inputs and the current value. Behind the scene, it uses the [FormData](https://developer.mozilla.org/docs/Web/API/FormData) constructor.
- `formElement`: It's the actual HTML form element in the DOM so for example you can access the `.reset()` method on a [HTMLFormElement](https://developer.mozilla.org/docs/Web/API/HTMLFormElement).
+## ⚙️ Advanced Usage
+
+This example shows how to use the `` component with `useForm` hook to validate the data with [Ajv JSON schema validator](https://ajv.js.org/).
+
+You can see a more detailled example in the [./example](./example) folder.
+
+```tsx
+import React from 'react'
+import { Form, useForm } from 'react-component-form'
+import type { HandleSubmitCallback } from 'react-component-form'
+
+const schema = {
+ inputName: {
+ type: 'string',
+ required: true,
+ minLength: 3,
+ maxLength: 10
+ }
+}
+
+const Example = () => {
+ const { errors, handleSubmit } = useForm(schema)
+
+ const onSubmit: HandleSubmitCallback = (formData, formElement) => {
+ console.log(formData) // { inputName: 'value of the input' }
+ formElement.reset()
+ }
+
+ return (
+
+ )
+}
+```
+
## 💡 Contributing
Anyone can help to improve the project, submit a Feature Request, a bug report or
diff --git a/example/package-lock.json b/example/package-lock.json
index 9287e35..f52c618 100644
--- a/example/package-lock.json
+++ b/example/package-lock.json
@@ -21,6 +21,11 @@
"..": {
"version": "0.0.0-development",
"license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "0.24.28",
+ "ajv": "8.11.0",
+ "ajv-formats": "2.1.1"
+ },
"devDependencies": {
"@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "17.0.3",
@@ -4356,12 +4361,15 @@
"requires": {
"@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "17.0.3",
+ "@sinclair/typebox": "0.24.28",
"@testing-library/react": "13.3.0",
"@types/jest": "28.1.8",
"@types/react": "18.0.17",
"@types/react-dom": "18.0.6",
"@typescript-eslint/eslint-plugin": "5.35.1",
"@typescript-eslint/parser": "5.35.1",
+ "ajv": "8.11.0",
+ "ajv-formats": "2.1.1",
"editorconfig-checker": "4.0.2",
"esbuild": "0.15.5",
"esbuild-jest": "0.5.0",
diff --git a/package-lock.json b/package-lock.json
index 0c04ba4..0bccbcb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,11 @@
"name": "react-component-form",
"version": "0.0.0-development",
"license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "0.24.28",
+ "ajv": "8.11.0",
+ "ajv-formats": "2.1.1"
+ },
"devDependencies": {
"@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "17.0.3",
@@ -2602,8 +2607,7 @@
"node_modules/@sinclair/typebox": {
"version": "0.24.28",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz",
- "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==",
- "dev": true
+ "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow=="
},
"node_modules/@sinonjs/commons": {
"version": "1.8.3",
@@ -3217,7 +3221,6 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
- "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -3229,6 +3232,22 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -6122,8 +6141,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-diff": {
"version": "1.2.0",
@@ -9897,8 +9915,7 @@
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -14081,7 +14098,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -14462,7 +14478,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16621,7 +16636,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
@@ -19041,8 +19055,7 @@
"@sinclair/typebox": {
"version": "0.24.28",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz",
- "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==",
- "dev": true
+ "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow=="
},
"@sinonjs/commons": {
"version": "1.8.3",
@@ -19523,7 +19536,6 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
- "dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -19531,6 +19543,14 @@
"uri-js": "^4.2.2"
}
},
+ "ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "requires": {
+ "ajv": "^8.0.0"
+ }
+ },
"ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -21631,8 +21651,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-diff": {
"version": "1.2.0",
@@ -24585,8 +24604,7 @@
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -27565,8 +27583,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "1.5.1",
@@ -27855,8 +27872,7 @@
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"requires-port": {
"version": "1.0.0",
@@ -29543,7 +29559,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"requires": {
"punycode": "^2.1.0"
}
diff --git a/package.json b/package.json
index fd6f3fa..b4b1525 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,11 @@
"peerDependencies": {
"react": ">=16"
},
+ "dependencies": {
+ "@sinclair/typebox": "0.24.28",
+ "ajv": "8.11.0",
+ "ajv-formats": "2.1.1"
+ },
"devDependencies": {
"@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "17.0.3",
@@ -46,6 +51,8 @@
"@typescript-eslint/eslint-plugin": "5.35.1",
"@typescript-eslint/parser": "5.35.1",
"editorconfig-checker": "4.0.2",
+ "esbuild": "0.15.5",
+ "esbuild-jest": "0.5.0",
"eslint": "8.22.0",
"eslint-config-conventions": "3.0.0",
"eslint-config-prettier": "8.5.0",
@@ -53,8 +60,6 @@
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "6.0.1",
"eslint-plugin-unicorn": "43.0.2",
- "esbuild": "0.15.5",
- "esbuild-jest": "0.5.0",
"jest": "29.0.0",
"jest-environment-jsdom": "29.0.0",
"markdownlint-cli2": "0.5.1",
diff --git a/src/index.tsx b/src/components/Form.tsx
similarity index 97%
rename from src/index.tsx
rename to src/components/Form.tsx
index f5c0b81..5ae4e36 100644
--- a/src/index.tsx
+++ b/src/components/Form.tsx
@@ -9,7 +9,7 @@ export type HandleForm = (
formElement: HTMLFormElement
) => void | Promise
-export interface ReactFormProps
+interface ReactFormProps
extends Omit, 'onSubmit' | 'onChange'> {}
export interface FormProps extends ReactFormProps {
diff --git a/src/hooks/useFetchState.ts b/src/hooks/useFetchState.ts
new file mode 100644
index 0000000..b65ea29
--- /dev/null
+++ b/src/hooks/useFetchState.ts
@@ -0,0 +1,15 @@
+import { useState } from 'react'
+
+export const fetchState = ['idle', 'loading', 'error', 'success'] as const
+
+export type FetchState = typeof fetchState[number]
+
+export const useFetchState = (
+ initialFetchState: FetchState = 'idle'
+): [
+ fetchState: FetchState,
+ setFetchState: React.Dispatch>
+] => {
+ const [fetchState, setFetchState] = useState(initialFetchState)
+ return [fetchState, setFetchState]
+}
diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts
new file mode 100644
index 0000000..2613c23
--- /dev/null
+++ b/src/hooks/useForm.ts
@@ -0,0 +1,144 @@
+import { useMemo, useState } from 'react'
+import { Static, TObject, TProperties, Type } from '@sinclair/typebox'
+import type { ErrorObject } from 'ajv'
+
+import type { HandleForm } from '../components/Form'
+import { FetchState, useFetchState } from './useFetchState'
+import { ajv } from '../utils/ajv'
+import { handleCheckboxBoolean } from '../utils/handleCheckboxBoolean'
+import { handleOptionalEmptyStringToNull } from '../utils/handleOptionalEmptyStringToNull'
+
+export type Error = ErrorObject
+
+export type ErrorsObject = {
+ [key in keyof Partial]: Error[] | undefined
+}
+
+export type HandleSubmitCallback = (
+ formData: Static>,
+ formElement: HTMLFormElement
+) => Promise | null>
+
+export type HandleSubmit = (
+ callback: HandleSubmitCallback
+) => HandleForm
+
+export interface GlobalMessage {
+ type: 'error' | 'success'
+ value?: string
+ properties?: undefined
+}
+
+export interface PropertiesMessage {
+ type: 'error'
+ value?: string
+ properties: { [key in keyof Partial]: string }
+}
+
+export type Message =
+ | GlobalMessage
+ | PropertiesMessage
+
+export interface UseFormResult {
+ handleSubmit: HandleSubmit
+
+ readonly fetchState: FetchState
+ setFetchState: React.Dispatch>
+
+ /**
+ * Global message of the form (not specific to a property).
+ */
+ readonly message: string | null
+ setMessage: React.Dispatch>
+
+ /**
+ * Errors for each property.
+ *
+ * The array will always have at least one element (never empty) in case of errors.
+ *
+ * `undefined` means no errors.
+ */
+ readonly errors: ErrorsObject
+}
+
+export const useForm = (
+ validationSchema: K
+): UseFormResult => {
+ const validationSchemaObject = useMemo(() => {
+ return Type.Object(validationSchema)
+ }, [validationSchema])
+
+ const [fetchState, setFetchState] = useFetchState()
+ const [message, setMessage] = useState(null)
+ const [errors, setErrors] = useState>(
+ {} as any
+ )
+
+ const validate = useMemo(() => {
+ return ajv.compile(validationSchemaObject)
+ }, [validationSchemaObject])
+
+ const handleSubmit: HandleSubmit = (callback) => {
+ return async (formData, formElement) => {
+ setErrors({} as any)
+ setMessage(null)
+ formData = handleOptionalEmptyStringToNull(
+ formData,
+ validationSchemaObject.required
+ )
+ formData = handleCheckboxBoolean(formData, validationSchemaObject)
+ const isValid = validate(formData)
+ if (!isValid) {
+ setFetchState('error')
+ const errors: ErrorsObject = {} as any
+ for (const property in validationSchemaObject.properties) {
+ const errorsForProperty = validate.errors?.filter((error) => {
+ return error.instancePath === `/${property}`
+ })
+ errors[property as keyof typeof validationSchema] =
+ errorsForProperty != null && errorsForProperty.length > 0
+ ? errorsForProperty
+ : undefined
+ }
+ setErrors(errors)
+ } else {
+ setErrors({} as any)
+ setFetchState('loading')
+ const message = await callback(
+ formData as Static>,
+ formElement
+ )
+ if (message != null) {
+ const { value = null, type, properties } = message
+ setMessage(value)
+ setFetchState(type)
+ if (type === 'error') {
+ const propertiesErrors: ErrorsObject =
+ {} as any
+ for (const property in properties) {
+ propertiesErrors[property] = [
+ {
+ keyword: 'message',
+ message: properties[property],
+ instancePath: `/${property}`,
+ schemaPath: `#/properties/${property}/message`,
+ params: {}
+ }
+ ]
+ }
+ setErrors(propertiesErrors)
+ }
+ }
+ }
+ }
+ }
+
+ return {
+ handleSubmit,
+ fetchState,
+ setFetchState,
+ message,
+ setMessage,
+ errors
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..62c763e
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,4 @@
+export * from './components/Form'
+export * from './hooks/useFetchState'
+export * from './hooks/useForm'
+export * from './utils/ajv'
diff --git a/src/utils/ajv.ts b/src/utils/ajv.ts
new file mode 100644
index 0000000..e97a6a6
--- /dev/null
+++ b/src/utils/ajv.ts
@@ -0,0 +1,24 @@
+import addFormats from 'ajv-formats'
+import Ajv from 'ajv'
+
+export const ajv = addFormats(
+ new Ajv({
+ allErrors: true
+ }),
+ [
+ 'date-time',
+ 'time',
+ 'date',
+ 'email',
+ 'hostname',
+ 'ipv4',
+ 'ipv6',
+ 'uri',
+ 'uri-reference',
+ 'uuid',
+ 'uri-template',
+ 'json-pointer',
+ 'relative-json-pointer',
+ 'regex'
+ ]
+)
diff --git a/src/utils/handleCheckboxBoolean.ts b/src/utils/handleCheckboxBoolean.ts
new file mode 100644
index 0000000..e9d8715
--- /dev/null
+++ b/src/utils/handleCheckboxBoolean.ts
@@ -0,0 +1,25 @@
+import type { TObject } from '@sinclair/typebox'
+
+import type { ObjectAny } from './types'
+
+export const handleCheckboxBoolean = (
+ object: ObjectAny,
+ validateSchemaObject: TObject
+): ObjectAny => {
+ const booleanProperties: string[] = []
+ for (const property in validateSchemaObject.properties) {
+ const rule = validateSchemaObject.properties[property]
+ if (rule.type === 'boolean') {
+ booleanProperties.push(property)
+ }
+ }
+ for (const booleanProperty of booleanProperties) {
+ if (object[booleanProperty] == null) {
+ object[booleanProperty] =
+ validateSchemaObject.properties[booleanProperty].default
+ } else {
+ object[booleanProperty] = object[booleanProperty] === 'on'
+ }
+ }
+ return object
+}
diff --git a/src/utils/handleOptionalEmptyStringToNull.ts b/src/utils/handleOptionalEmptyStringToNull.ts
new file mode 100644
index 0000000..278a0c4
--- /dev/null
+++ b/src/utils/handleOptionalEmptyStringToNull.ts
@@ -0,0 +1,17 @@
+export const handleOptionalEmptyStringToNull = (
+ object: K,
+ required: string[] = []
+): K => {
+ return Object.fromEntries(
+ Object.entries(object).map(([key, value]) => {
+ if (
+ typeof value === 'string' &&
+ value.length === 0 &&
+ !required.includes(key)
+ ) {
+ return [key, null]
+ }
+ return [key, value]
+ })
+ ) as K
+}
diff --git a/src/utils/types.ts b/src/utils/types.ts
new file mode 100644
index 0000000..bd2c728
--- /dev/null
+++ b/src/utils/types.ts
@@ -0,0 +1,3 @@
+export interface ObjectAny {
+ [key: string]: any
+}
diff --git a/tsup.config.js b/tsup.config.js
index 7658762..a4d26f7 100644
--- a/tsup.config.js
+++ b/tsup.config.js
@@ -1,7 +1,7 @@
import { defineConfig } from 'tsup'
export default defineConfig({
- entry: ['src/index.tsx'],
+ entry: ['src/index.ts'],
sourcemap: true,
clean: true,
platform: 'browser',