├── .eslintignore ├── src ├── types │ ├── index.ts │ └── hooks.ts ├── index.ts ├── hooks │ ├── useInertiaPrecognitionForm.ts │ ├── usePrecognitionForm.ts │ └── usePrecognition.ts └── helpers │ └── patchInertiaForm.ts ├── .gitignore ├── .prettierrc.json ├── .github └── workflows │ └── publish.yml ├── LICENSE ├── tsconfig.json ├── package.json ├── README.md └── .eslintrc /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | yarn-error.log 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks/useInertiaPrecognitionForm'; 2 | export * from './hooks/usePrecognition'; 3 | export * from './hooks/usePrecognitionForm'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-npm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup Node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: 16.x 19 | registry-url: 'https://npm.pkg.github.com' 20 | scope: '@lifespikes' 21 | 22 | - name: Install dependencies 23 | run: yarn 24 | - name: Publish 25 | run: yarn npm:publish 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 FelipeVa 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 | -------------------------------------------------------------------------------- /src/hooks/useInertiaPrecognitionForm.ts: -------------------------------------------------------------------------------- 1 | import { useForm } from '@inertiajs/react'; 2 | import { useMemo } from 'react'; 3 | import { 4 | UseInertiaPrecognitionFormReturn, 5 | UsePrecognitionFormProps, 6 | } from '../types'; 7 | import { usePrecognition } from './usePrecognition'; 8 | import { patchInertiaForm } from '../helpers/patchInertiaForm'; 9 | 10 | export function useInertiaPrecognitionForm< 11 | TForm extends Record 12 | >({ 13 | precognition: { method, url, config }, 14 | form: { initialValues }, 15 | }: UsePrecognitionFormProps): UseInertiaPrecognitionFormReturn { 16 | const inertia = useForm(initialValues); 17 | const { touched, validator, setLastTouched, ...rest } = 18 | usePrecognition(method, url, inertia.data, config); 19 | const passed = useMemo( 20 | () => 21 | touched.filter( 22 | (field: keyof TForm) => typeof inertia.errors[field] === 'undefined' 23 | ), 24 | [inertia.errors, touched] 25 | ); 26 | 27 | const patchedForm = patchInertiaForm(method, url, inertia, validator); 28 | 29 | return Object.assign(patchedForm, { 30 | ...rest, 31 | validateAndSetDataByKeyValuePair(name: keyof TForm, value: any) { 32 | patchedForm.setData(name, value); 33 | setLastTouched(name); 34 | 35 | return this; 36 | }, 37 | validator, 38 | touched, 39 | passed, 40 | setLastTouched, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src"], 4 | "exclude": [ 5 | "dist", 6 | "node_modules" 7 | ], 8 | "compilerOptions": { 9 | "module": "esnext", 10 | "lib": ["dom", "esnext"], 11 | "importHelpers": true, 12 | // output .d.ts declaration files for consumers 13 | "declaration": true, 14 | // output .js.map sourcemap files for consumers 15 | "sourceMap": true, 16 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 17 | "rootDir": "./src", 18 | // stricter type-checking for stronger correctness. Recommended by TS 19 | "strict": true, 20 | // linter checks for common issues 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true, 26 | // use Node's module resolution algorithm, instead of the legacy TS one 27 | "moduleResolution": "node", 28 | // transpile JSX to React.createElement 29 | "jsx": "react", 30 | // interop between ESM and CJS modules. Recommended by TS 31 | "esModuleInterop": true, 32 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 33 | "skipLibCheck": true, 34 | // error out if import and file system have a casing mismatch. Recommended by TS 35 | "forceConsistentCasingInFileNames": true, 36 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 37 | "noEmit": true, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/hooks/usePrecognitionForm.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react'; 2 | import { toSimpleValidationErrors } from 'laravel-precognition'; 3 | import { UsePrecognitionFormReturn, UsePrecognitionFormProps } from '../types'; 4 | import { usePrecognition } from './usePrecognition'; 5 | 6 | export function usePrecognitionForm>({ 7 | precognition: { method, url, config }, 8 | form: { initialValues }, 9 | }: UsePrecognitionFormProps): UsePrecognitionFormReturn { 10 | const [data, setData] = useState(initialValues); 11 | const [errors, setErrors] = useState | null>( 12 | null 13 | ); 14 | const clearErrors = () => setErrors(null); 15 | const { validator, setLastTouched, touched, ...rest } = usePrecognition( 16 | method, 17 | url, 18 | data, 19 | config 20 | ); 21 | 22 | validator.on('validatingChanged', () => { 23 | clearErrors(); 24 | setErrors( 25 | toSimpleValidationErrors(validator.errors()) as unknown as Record< 26 | keyof TForm, 27 | string 28 | > 29 | ); 30 | }); 31 | 32 | const passed = useMemo( 33 | () => 34 | touched.filter( 35 | (field: keyof TForm) => errors && typeof errors[field] === 'undefined' 36 | ), 37 | [errors, touched] 38 | ); 39 | 40 | return { 41 | ...rest, 42 | validator, 43 | setLastTouched, 44 | touched, 45 | data, 46 | setData, 47 | errors, 48 | setErrors, 49 | clearErrors, 50 | validateAndSetDataByKeyValuePair(name: keyof TForm, value: any) { 51 | setData({ 52 | ...data, 53 | [name]: value, 54 | }); 55 | 56 | setLastTouched(name); 57 | 58 | return this; 59 | }, 60 | passed, 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/types/hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Config, 3 | RequestMethods, 4 | Timeout, 5 | Validator, 6 | } from 'laravel-precognition/dist/types'; 7 | import { Dispatch, SetStateAction } from 'react'; 8 | import { InertiaFormProps } from '@inertiajs/react/types/useForm'; 9 | 10 | export type UsePrecognitionFormProps> = { 11 | precognition: { 12 | method: RequestMethods; 13 | url: string; 14 | config?: Config; 15 | }; 16 | 17 | form: { 18 | initialValues: TForm; 19 | }; 20 | }; 21 | 22 | export type UsePrecognitionReturn> = { 23 | validator: Validator; 24 | validate(name: keyof TForm): UsePrecognitionReturn; 25 | isValidating: string | null; 26 | isProcessingValidation: boolean; 27 | lastTouched: keyof TForm | null; 28 | setLastTouched: Dispatch>; 29 | touched: Array; 30 | 31 | setValidatorTimeout(duration: Timeout): UsePrecognitionReturn; 32 | }; 33 | 34 | export type InertiaPrecognitionFormProps< 35 | TForm extends Record 36 | > = InertiaFormProps & UsePrecognitionReturn; 37 | 38 | export interface UseInertiaPrecognitionFormReturn< 39 | TForm extends Record 40 | > extends InertiaPrecognitionFormProps { 41 | validateAndSetDataByKeyValuePair( 42 | name: keyof TForm, 43 | value: any 44 | ): UsePrecognitionReturn; 45 | passed: Array; 46 | } 47 | 48 | export type UsePrecognitionFormReturn> = 49 | UsePrecognitionReturn & { 50 | data: TForm; 51 | setData: Dispatch>; 52 | errors: Record | null; 53 | setErrors: Dispatch | null>>; 54 | clearErrors: () => void; 55 | validateAndSetDataByKeyValuePair( 56 | key: keyof TForm, 57 | value: any 58 | ): UsePrecognitionFormReturn; 59 | passed: Array; 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifespikes/laravel-precognition-react", 3 | "version": "0.1.2", 4 | "description": "A set of React Hooks for Laravel Precognition", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "module": "dist/laravel-precognition-react.esm.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/lifespikes/laravel-precognition-react.git" 11 | }, 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "size": "size-limit", 16 | "lint": "eslint --ext .ts ./src", 17 | "lint:fix": "eslint --fix --ext .ts ./src", 18 | "prettier": "prettier --write \"{src,tests,example/src}/**/*.{js,ts,jsx,tsx}\"", 19 | "prepare": "npm run build", 20 | "prepublishOnly": "npm run prettier && npm run lint", 21 | "npm:publish": "yarn build && yarn size && npm publish --access=public" 22 | }, 23 | "files": [ 24 | "dist", 25 | "src" 26 | ], 27 | "engines": { 28 | "node": ">=10" 29 | }, 30 | "keywords": [ 31 | "react", 32 | "laravel", 33 | "precognition", 34 | "inertiajs", 35 | "laravel-precognition" 36 | ], 37 | "author": "Felipe Valencia", 38 | "license": "MIT", 39 | "peerDependencies": { 40 | "react": ">=16" 41 | }, 42 | "size-limit": [ 43 | { 44 | "path": "dist/laravel-precognition-react.cjs.production.min.js", 45 | "limit": "50 KB" 46 | }, 47 | { 48 | "path": "dist/laravel-precognition-react.esm.js", 49 | "limit": "50 KB" 50 | } 51 | ], 52 | "devDependencies": { 53 | "@size-limit/preset-small-lib": "^8.1.0", 54 | "@types/react": "^18.0.26", 55 | "@typescript-eslint/eslint-plugin": "^5.46.0", 56 | "@typescript-eslint/parser": "^5.46.0", 57 | "eslint": "^8.29.0", 58 | "eslint-config-prettier": "^8.5.0", 59 | "eslint-plugin-prettier": "^4.2.1", 60 | "eslint-plugin-react": "^7.31.11", 61 | "eslint-plugin-react-hooks": "^4.6.0", 62 | "prettier": "^2.8.1", 63 | "react": "^18.2.0", 64 | "react-dom": "^18.2.0", 65 | "size-limit": "^8.1.0", 66 | "tsdx": "^0.14.1", 67 | "typescript": "^4.9.4" 68 | }, 69 | "dependencies": { 70 | "@inertiajs/react": "^1.0.0", 71 | "laravel-precognition": "^0.1.0" 72 | }, 73 | "bugs": { 74 | "url": "https://github.com/lifespikes/laravel-precognition-react/issues" 75 | }, 76 | "homepage": "https://github.com/lifespikes/laravel-precognition-react#readme" 77 | } 78 | -------------------------------------------------------------------------------- /src/helpers/patchInertiaForm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RequestMethods, 3 | SimpleValidationErrors, 4 | ValidationErrors, 5 | Validator, 6 | } from 'laravel-precognition/dist/types'; 7 | import { toSimpleValidationErrors } from 'laravel-precognition'; 8 | import { InertiaFormProps } from '@inertiajs/react/types/useForm'; 9 | import { VisitOptions, Method } from '@inertiajs/core/types/types'; 10 | 11 | const getOptionsWithValidator = ( 12 | validator: Validator, 13 | options?: VisitOptions 14 | ) => { 15 | const _options = options || {}; 16 | 17 | return { 18 | ..._options, 19 | onError: (errors: ValidationErrors | SimpleValidationErrors) => { 20 | validator.setErrors(errors); 21 | 22 | if (options?.onError) { 23 | options.onError(errors as unknown as any); 24 | } 25 | }, 26 | }; 27 | }; 28 | 29 | export const patchInertiaForm = >( 30 | method: RequestMethods, 31 | url: string, 32 | form: InertiaFormProps, 33 | validator: Validator 34 | ): InertiaFormProps => { 35 | const binds = { 36 | submit: form.submit.bind(form), 37 | get: form.get.bind(form), 38 | put: form.put.bind(form), 39 | post: form.post.bind(form), 40 | delete: form.delete.bind(form), 41 | patch: form.patch.bind(form), 42 | }; 43 | 44 | validator.on('validatingChanged', () => { 45 | form.clearErrors(); 46 | form.setError( 47 | toSimpleValidationErrors(validator.errors()) as unknown as Record< 48 | keyof TForm, 49 | string 50 | > 51 | ); 52 | }); 53 | 54 | const bindsFactory = Object.keys(binds) 55 | .filter((bind) => bind !== 'submit') 56 | .reduce((acc, item) => { 57 | const obj: Record = {}; 58 | 59 | obj[item] = (u: string, o?: VisitOptions) => { 60 | const options = getOptionsWithValidator(validator, o); 61 | 62 | binds[item as Exclude](u, options); 63 | }; 64 | 65 | return { 66 | ...acc, 67 | ...obj, 68 | }; 69 | }, {}); 70 | 71 | return Object.assign(form, { 72 | ...bindsFactory, 73 | submit(m: RequestMethods, u: string, o?: VisitOptions) { 74 | const hasDifferentMethod = m.length > 0; 75 | const options = getOptionsWithValidator(validator, o); 76 | 77 | binds.submit( 78 | (hasDifferentMethod ? m : method) as Method, 79 | hasDifferentMethod ? url : u, 80 | options 81 | ); 82 | }, 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /src/hooks/usePrecognition.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This Hook is based on Laravel Precognition (Vue) 3 | * @see https://github.com/laravel/precognition-vue 4 | */ 5 | import { useEffect, useState } from 'react'; 6 | import precognitive from 'laravel-precognition'; 7 | import { 8 | Config, 9 | RequestMethods, 10 | Timeout, 11 | } from 'laravel-precognition/dist/types'; 12 | import { UsePrecognitionReturn } from '../types'; 13 | 14 | export function usePrecognition>( 15 | method: RequestMethods, 16 | url: string, 17 | data: TForm, 18 | config?: Config 19 | ): UsePrecognitionReturn { 20 | const [isValidating, setIsValidating] = useState(null); 21 | const [isProcessingValidation, setIsProcessingValidation] = useState(false); 22 | const [touched, setTouched] = useState>([]); 23 | const [lastTouched, setLastTouched] = useState(null); 24 | 25 | const validator = precognitive.validate((client) => { 26 | const lowerCasedMethod = method.toLowerCase(); 27 | 28 | if (lowerCasedMethod === 'get' || lowerCasedMethod === 'delete') { 29 | return client[lowerCasedMethod](url, config); 30 | } 31 | 32 | return client[ 33 | lowerCasedMethod as Exclude 34 | ](url, data, config); 35 | }); 36 | 37 | useEffect(() => { 38 | setIsValidating(validator.validating()); 39 | setIsProcessingValidation(validator.processingValidation()); 40 | setTouched(validator.touched() as unknown as Array); 41 | }, []); 42 | 43 | useEffect(() => { 44 | if (lastTouched) { 45 | validator.validate(lastTouched as unknown as string); 46 | setLastTouched(null); 47 | } 48 | }, [lastTouched]); 49 | 50 | validator.on('validatingChanged', () => 51 | setIsValidating(validator.validating()) 52 | ); 53 | 54 | validator.on('processingValidationChanged', () => 55 | setIsProcessingValidation(validator.processingValidation()) 56 | ); 57 | 58 | validator.on('touchedChanged', () => 59 | setTouched(validator.touched() as unknown as Array) 60 | ); 61 | 62 | return { 63 | validator, 64 | validate(name: keyof TForm) { 65 | setLastTouched(name); 66 | 67 | return this; 68 | }, 69 | isValidating, 70 | isProcessingValidation, 71 | lastTouched, 72 | setLastTouched, 73 | touched, 74 | setValidatorTimeout(duration: Timeout) { 75 | validator.setTimeout(duration); 76 | return this; 77 | }, 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # @lifespikes/laravel-precognition-react 3 | 4 | [](https://www.npmjs.com/package/@lifespikes/laravel-precognition-react) 5 | 6 | 7 | [](https://bundlephobia.com/package/@lifespikes/laravel-precognition-react) 8 | 9 | 10 | A set of React hooks that integrates [Laravel Precognition](https://github.com/laravel/framework/pull/44339) with Inertia.js and simple forms.
11 | The hooks are highly based on [laravel-precognition-vue](https://www.npmjs.com/package/laravel-precognition-vue?activeTab=readme). 12 | 13 | # Install 14 | 15 | #### Using npm 16 | 17 | ```bash 18 | npm i @lifespikes/laravel-precognition-react 19 | ``` 20 | 21 | #### Using Yarn 22 | 23 | ```bash 24 | yarn add @lifespikes/laravel-precognition-react 25 | ``` 26 | 27 | #### Using pnpm 28 | 29 | ```bash 30 | pnpm add @lifespikes/laravel-precognition-react 31 | ``` 32 | 33 | # Usage 34 | The package provides two hooks: `usePrecognitionForm` and `useInertiaPrecognitionForm`. 35 | 36 | Both hooks receive a `props` object, that follows the following structure: 37 | 38 | ```typescript 39 | { 40 | precognition: { // This is where the precognition configuration 41 | method: 'put' // PUT, PATCH, POST, DELETE 42 | url: 'https://example.com' // URL to send the precognition request 43 | }, 44 | form: { 45 | initialValues: { 46 | example: 'value value' 47 | } // Initial values of the form 48 | } 49 | } 50 | ``` 51 | 52 | Also, both hooks return the next properties and methods related to the `usePrecognition` hook: 53 | 54 | - `validator` - The validator instance 55 | - `validate(name)` - Validate a specific field 56 | - `isValidating` - Whether the form is validating 57 | - `isProcessingValidation` - Whether the form is processing the validation 58 | - `lastTouched` - The last touched field 59 | - `setLastTouched` - Set the last touched field(This will also trigger the validation) 60 | - `touched` - The touched fields, 61 | - `setValidatorTimeout(duration)` - Set the timeout for the validation. 62 | 63 | ### 1. Using with Inertia 64 | For using with Inertia.js, you need to import `useInertiaPrecognitionForm` hook.
65 | 66 | The `useInertiaPrecognitionForm` hook uses [Inertia.js](https://inertiajs.com/forms) `useForm` form helper hook under the hood. So it'll return the same properties and methods as the `useForm` hook. 67 | 68 | #### Usage example: 69 | ```tsx 70 | import React, { ChangeEvent, FormEvent } from 'react'; 71 | import {useInertiaPrecognitionForm} from '@lifespikes/laravel-precognition-react'; 72 | 73 | type FormFields = { 74 | name: string; 75 | email: string; 76 | } 77 | 78 | const ExampleForm = () => { 79 | const route = 'https://example.com/user/1'; 80 | const { 81 | data, 82 | setData, 83 | put, 84 | processing, 85 | errors, 86 | validateAndSetDataByKeyValuePair, 87 | isProcessingValidation, 88 | } = useInertiaPrecognitionForm({ 89 | precognition: { 90 | method: 'put', 91 | url: route, 92 | }, 93 | form: { 94 | initialValues: { 95 | name: '', 96 | email: '', 97 | }, 98 | }, 99 | }); 100 | 101 | const onHandleChange = (event: ChangeEvent) => { 102 | // This will validate the input and update the form data 103 | validateAndSetDataByKeyValuePair( 104 | event.target.name as keyof FormFields, 105 | event.target.value, 106 | ); 107 | } 108 | 109 | const onSubmit = (event: FormEvent) => { 110 | event.preventDefault(); 111 | 112 | return put(route, { 113 | onSuccess: () => { 114 | setData({ 115 | name: '', 116 | email: '', 117 | }); 118 | }, 119 | }); 120 | }; 121 | 122 | return ( 123 |
124 |
125 | 131 | {errors.name ?

{errors.name}

: null} 132 |
133 |
134 | 140 | {errors.name ?

{errors.email}

: null} 141 |
142 | 145 |
146 | ); 147 | } 148 | ``` 149 | 150 | ### 2. Using with a simple form 151 | 152 | This is a small example of the usage of the `usePrecognitionForm` hook with a simple/basic form. 153 | 154 | Also, this hook returns the following additional properties: 155 | 156 | - `data` - The form data. 157 | - `setData` - A function that sets the form data. 158 | - `errors` - The form errors. 159 | - `setErrors` - A function that sets the form errors. 160 | - `clearErrors` - A function that clears the form errors. 161 | 162 | #### Usage example: 163 | ```tsx 164 | import React, { ChangeEvent, FormEvent } from 'react'; 165 | import {usePrecognitionForm} from '@lifespikes/laravel-precognition-react'; 166 | 167 | type FormFields = { 168 | name: string; 169 | email: string; 170 | } 171 | 172 | const ExampleForm = () => { 173 | const route = 'https://example.com/user/1'; 174 | const { 175 | data, 176 | setData, 177 | errors, 178 | validateAndSetDataByKeyValuePair, 179 | isProcessingValidation, 180 | } = usePrecognitionForm({ 181 | precognition: { 182 | method: 'put', 183 | url: route, 184 | }, 185 | form: { 186 | initialValues: { 187 | name: '', 188 | email: '', 189 | }, 190 | }, 191 | }); 192 | 193 | const onHandleChange = (event: ChangeEvent) => { 194 | // This will validate the input and update the form data 195 | validateAndSetDataByKeyValuePair( 196 | event.target.name as keyof FormFields, 197 | event.target.value, 198 | ); 199 | } 200 | 201 | const onSubmit = (event: FormEvent) => { 202 | event.preventDefault(); 203 | 204 | // Implement your own logic here 205 | }; 206 | 207 | return ( 208 |
209 |
210 | 216 | {errors.name ?

{errors.name}

: null} 217 |
218 |
219 | 225 | {errors.name ?

{errors.email}

: null} 226 |
227 | 230 |
231 | ); 232 | } 233 | ``` 234 | 235 | ## Notes 236 | 237 | - This is not an official package. It's just a personal attempt to create a package that integrates Laravel Precognition with React. 238 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "plugin:prettier/recommended", 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "plugins": [ 13 | "@typescript-eslint", 14 | "prettier", 15 | "react", 16 | "react-hooks" 17 | ], 18 | "rules": { 19 | "prettier/prettier": "error", 20 | "@typescript-eslint/no-unused-vars": "error", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-var-requires": "off", 23 | "@typescript-eslint/no-non-null-assertion": "off", 24 | "@typescript-eslint/no-empty-interface": "off", 25 | "@typescript-eslint/explicit-module-boundary-types": "off", 26 | "@typescript-eslint/member-delimiter-style": "off", 27 | "no-empty": "off", 28 | "no-empty-pattern": "off", 29 | "react/prop-types": "off", 30 | "accessor-pairs": "error", 31 | "array-bracket-newline": "off", 32 | "array-bracket-spacing": "error", 33 | "array-callback-return": "off", 34 | "array-element-newline": [ 35 | "error", 36 | "consistent" 37 | ], 38 | "arrow-body-style": "off", 39 | "arrow-parens": "off", 40 | "arrow-spacing": "error", 41 | "block-scoped-var": "error", 42 | "block-spacing": "error", 43 | "brace-style": "error", 44 | "camelcase": "off", 45 | "comma-dangle": "off", 46 | "comma-spacing": "error", 47 | "comma-style": "error", 48 | "complexity": "off", 49 | "computed-property-spacing": "error", 50 | "consistent-this": "error", 51 | "default-case-last": "error", 52 | "default-param-last": "off", 53 | "dot-location": [ 54 | "error", 55 | "property" 56 | ], 57 | "dot-notation": "error", 58 | "eol-last": "error", 59 | "eqeqeq": "error", 60 | "func-call-spacing": "off", 61 | "func-name-matching": "error", 62 | "func-names": "error", 63 | "func-style": [ 64 | "error", 65 | "declaration", 66 | { 67 | "allowArrowFunctions": true 68 | } 69 | ], 70 | "function-call-argument-newline": [ 71 | "error", 72 | "consistent" 73 | ], 74 | "function-paren-newline": "off", 75 | "generator-star-spacing": "error", 76 | "global-require": "error", 77 | "grouped-accessor-pairs": "error", 78 | "guard-for-in": "error", 79 | "handle-callback-err": "error", 80 | "id-blacklist": "error", 81 | "id-denylist": "error", 82 | "id-match": "error", 83 | "indent": [ 84 | "off", 85 | 2, 86 | { 87 | "offsetTernaryExpressions": true 88 | } 89 | ], 90 | "init-declarations": "error", 91 | "jsx-quotes": "error", 92 | "key-spacing": [ 93 | "error" 94 | ], 95 | "keyword-spacing": "off", 96 | "line-comment-position": "off", 97 | "linebreak-style": "off", 98 | "lines-around-comment": "off", 99 | "lines-around-directive": "error", 100 | "lines-between-class-members": "error", 101 | "max-classes-per-file": "error", 102 | "max-depth": "error", 103 | "max-len": "off", 104 | "max-lines": "off", 105 | "max-lines-per-function": "off", 106 | "max-nested-callbacks": "error", 107 | "max-statements": "off", 108 | "max-statements-per-line": "off", 109 | "multiline-comment-style": "off", 110 | "multiline-ternary": "off", 111 | "new-cap": "error", 112 | "new-parens": "error", 113 | "newline-after-var": "off", 114 | "newline-before-return": "off", 115 | "newline-per-chained-call": "off", 116 | "no-alert": "error", 117 | "no-array-constructor": "error", 118 | "no-await-in-loop": "off", 119 | "no-bitwise": "off", 120 | "no-buffer-constructor": "error", 121 | "no-caller": "error", 122 | "no-catch-shadow": "error", 123 | "no-console": "off", 124 | "no-constructor-return": "error", 125 | "no-continue": "error", 126 | "no-div-regex": "error", 127 | "no-duplicate-imports": "error", 128 | "@typescript-eslint/no-empty-function": "off", 129 | "no-eq-null": "error", 130 | "no-eval": "error", 131 | "no-extend-native": "error", 132 | "no-extra-bind": "error", 133 | "no-extra-label": "error", 134 | "no-extra-parens": "off", 135 | "no-floating-decimal": "error", 136 | "no-implicit-coercion": "off", 137 | "no-implicit-globals": "error", 138 | "no-implied-eval": "error", 139 | "no-inline-comments": "off", 140 | "no-invalid-this": "error", 141 | "no-iterator": "error", 142 | "no-label-var": "error", 143 | "no-labels": "error", 144 | "no-lone-blocks": "error", 145 | "no-lonely-if": "error", 146 | "no-loop-func": "error", 147 | "no-loss-of-precision": "error", 148 | "@typescript-eslint/no-magic-numbers": "off", 149 | "no-mixed-operators": "off", 150 | "no-mixed-requires": "error", 151 | "no-multi-assign": "error", 152 | "no-multi-spaces": "error", 153 | "no-multi-str": "error", 154 | "no-multiple-empty-lines": "error", 155 | "no-native-reassign": "error", 156 | "no-negated-condition": "off", 157 | "no-negated-in-lhs": "error", 158 | "no-nested-ternary": "off", 159 | "no-new": "error", 160 | "no-new-func": "error", 161 | "no-new-object": "error", 162 | "no-new-require": "error", 163 | "no-new-wrappers": "error", 164 | "no-nonoctal-decimal-escape": "error", 165 | "no-octal-escape": "error", 166 | "no-param-reassign": "error", 167 | "no-path-concat": "error", 168 | "no-plusplus": "off", 169 | "no-process-env": "off", 170 | "no-process-exit": "error", 171 | "no-promise-executor-return": "off", 172 | "no-proto": "error", 173 | "no-restricted-exports": "error", 174 | "no-restricted-globals": "error", 175 | "no-restricted-imports": "error", 176 | "no-restricted-modules": "error", 177 | "no-restricted-properties": "error", 178 | "no-restricted-syntax": "error", 179 | "no-script-url": "error", 180 | "no-self-compare": "error", 181 | "no-sequences": "error", 182 | "@typescript-eslint/no-shadow": "error", 183 | "no-spaced-func": "off", 184 | "no-sync": "error", 185 | "no-tabs": "error", 186 | "no-template-curly-in-string": "error", 187 | "no-throw-literal": "error", 188 | "no-trailing-spaces": "error", 189 | "no-undef-init": "off", 190 | "no-undefined": "off", 191 | "no-underscore-dangle": "off", 192 | "no-unmodified-loop-condition": "error", 193 | "no-unneeded-ternary": "error", 194 | "no-unreachable-loop": "error", 195 | "no-unsafe-optional-chaining": "error", 196 | "no-unused-expressions": "error", 197 | "@typescript-eslint/no-use-before-define": "error", 198 | "no-useless-backreference": "error", 199 | "no-useless-call": "error", 200 | "no-useless-computed-key": "error", 201 | "no-useless-concat": "error", 202 | "no-useless-constructor": "error", 203 | "no-useless-rename": "error", 204 | "no-useless-return": "error", 205 | "no-var": "error", 206 | "no-void": "error", 207 | "no-warning-comments": "off", 208 | "no-whitespace-before-property": "error", 209 | "nonblock-statement-body-position": "error", 210 | "object-curly-newline": "error", 211 | "object-curly-spacing": [ 212 | "error", 213 | "always" 214 | ], 215 | "object-property-newline": [ 216 | "error", 217 | { 218 | "allowAllPropertiesOnSameLine": true 219 | } 220 | ], 221 | "object-shorthand": "error", 222 | "one-var": [ 223 | "error", 224 | "never" 225 | ], 226 | "operator-assignment": "error", 227 | "operator-linebreak": "error", 228 | "padded-blocks": [ 229 | "error", 230 | "never" 231 | ], 232 | "padding-line-between-statements": "off", 233 | "prefer-arrow-callback": "off", 234 | "prefer-const": "error", 235 | "prefer-destructuring": "off", 236 | "prefer-exponentiation-operator": "error", 237 | "prefer-named-capture-group": "off", 238 | "prefer-numeric-literals": "error", 239 | "prefer-object-spread": "error", 240 | "prefer-promise-reject-errors": "error", 241 | "prefer-reflect": "error", 242 | "prefer-regex-literals": "error", 243 | "prefer-rest-params": "error", 244 | "prefer-spread": "error", 245 | "prefer-template": "off", 246 | "quote-props": "off", 247 | "radix": "off", 248 | "require-atomic-updates": "error", 249 | "require-await": "error", 250 | "require-unicode-regexp": "off", 251 | "rest-spread-spacing": "error", 252 | "semi-spacing": "error", 253 | "semi-style": "error", 254 | "no-extra-semi": "error", 255 | "space-before-blocks": "error", 256 | "space-before-function-paren": [ 257 | "error", 258 | { 259 | "anonymous": "never", 260 | "named": "never", 261 | "asyncArrow": "always" 262 | } 263 | ], 264 | "space-in-parens": [ 265 | "error", 266 | "never" 267 | ], 268 | "space-infix-ops": "error", 269 | "space-unary-ops": [ 270 | "error", 271 | { 272 | "words": true, 273 | "nonwords": false 274 | } 275 | ], 276 | "spaced-comment": "error", 277 | "strict": "error", 278 | "switch-colon-spacing": "error", 279 | "symbol-description": "error", 280 | "template-curly-spacing": "error", 281 | "template-tag-spacing": "error", 282 | "unicode-bom": "error", 283 | "valid-jsdoc": "error", 284 | "vars-on-top": "error", 285 | "wrap-iife": "error", 286 | "wrap-regex": "off", 287 | "yield-star-spacing": "error", 288 | "yoda": "error", 289 | "@typescript-eslint/ban-types": [ 290 | "warn" 291 | ], 292 | "react/react-in-jsx-scope": "off" 293 | }, 294 | "settings": { 295 | "react": { 296 | "version": "detect" 297 | } 298 | }, 299 | "env": { 300 | "browser": true, 301 | "node": true 302 | }, 303 | "globals": { 304 | "JSX": true 305 | } 306 | } 307 | --------------------------------------------------------------------------------