Skip to content

Commit 0c82dce

Browse files
committed
Introduce more options
1 parent a1ee7f7 commit 0c82dce

File tree

5 files changed

+123
-18
lines changed

5 files changed

+123
-18
lines changed

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,24 @@ This library is a part of the toolkit used extensively on [Programmer Network](h
1414
- **Plugin Extensibility**: Supports Ajv [plugins](https://ajv.js.org/packages/) for custom validators, enhancing schema flexibility.
1515
- **Design Agnostic**: Offers full freedom in how you structure, style, and handle your forms.
1616

17-
## Features
18-
19-
- **JSON Schema Validation**: Validates form data against a specified JSON schema.
20-
- **Dirty State Tracking**: Identifies changes in form fields.
21-
- **Remote Error Handling**: Manages errors from external sources (like APIs) as part of schema validation.
22-
2317
## Installation
2418

2519
```bash
2620
pnpm add @programmer_network/use-ajv-form
27-
# or
28-
bun install @programmer_network/use-ajv-form
29-
# or
30-
yarn add @programmer_network/use-ajv-form
31-
# or
32-
npm install @programmer_network/use-ajv-form
3321
```
22+
23+
## Options
24+
25+
Below is a table describing the options you can pass to `useAJVForm`:
26+
27+
| Option | Type | Description |
28+
| --------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------ |
29+
| `customKeywords` | `KeywordDefinition[]` | Custom AJV keywords for additional validation logic. |
30+
| `errors` | `ErrorObject[]` | Pre-defined errors to set initial form errors. This could also be errors originating from your API. |
31+
| `userDefinedMessages` | `Record<string, AJVMessageFunction>` | Custom error messages for validation errors. |
32+
| `shouldDebounceAndValidate` | `boolean` | If `true`, enables debouncing for field validation. |
33+
| `debounceTime` | `number` | Time in milliseconds for debouncing validation. Ignore if `shouldDebounceAndValidate` is set to false. |
34+
35+
## Usage
36+
37+
Here's a basic example of using `useAJVForm` inside of our [Yail Storybook](https://yail.programmer.network/?path=/story/input-forms--use-ajv-form)

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@programmer_network/use-ajv-form",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "Custom React Hook that integrates with Ajv JSON Schema Validator",
55
"main": "dist/use-ajv-form.es.js",
66
"author": "Aleksandar Grbic",
@@ -73,13 +73,13 @@
7373
"husky": "8.0.3",
7474
"jsdom": "22.1.0",
7575
"prettier": "3.0.3",
76+
"programmer-network-ajv": "github:Programmer-Network/Programmer-Network-AJV",
7677
"react-hooks": "link:@types/@testing-library/react-hooks",
7778
"typescript": "5.2.2",
7879
"vite": "5.0.2",
7980
"vite-plugin-dts": "3.6.3",
8081
"vite-tsconfig-paths": "4.2.1",
81-
"vitest": "0.34.6",
82-
"programmer-network-ajv": "github:Programmer-Network/Programmer-Network-AJV"
82+
"vitest": "0.34.6"
8383
},
8484
"files": [
8585
"dist"

src/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const useAJVForm = <T extends Record<string, any>>(
1919
customKeywords?: KeywordDefinition[];
2020
errors?: ErrorObject[];
2121
userDefinedMessages?: Record<string, AJVMessageFunction>;
22+
shouldDebounceAndValidate?: boolean;
23+
debounceTime?: number;
2224
},
2325
): UseFormReturn<T> => {
2426
const initialStateRef = useRef<IState<T>>(getInitial(initial));
@@ -30,7 +32,7 @@ const useAJVForm = <T extends Record<string, any>>(
3032
editId: number;
3133
} | null>(null);
3234
const [editCounter, setEditCounter] = useState(0);
33-
const debouncedField = useDebounce(currentField, 500);
35+
const debouncedField = useDebounce(currentField, options?.debounceTime || 500);
3436

3537
if (options?.customKeywords?.length) {
3638
addUserDefinedKeywords(ajv, options.customKeywords);
@@ -151,15 +153,23 @@ const useAJVForm = <T extends Record<string, any>>(
151153
);
152154
};
153155

156+
const isFormValid = (currentState: IState<T>): boolean => {
157+
return !Object.keys(currentState).some((key) => currentState[key].error !== '');
158+
};
159+
154160
const isDirty = useMemo(
155161
() => isFormDirty(state, initialStateRef.current),
156162
[state],
157163
);
158164

165+
const isValid = useMemo(() => isFormValid(state), [state]);
166+
159167
useEffect(() => {
160-
if (debouncedField) {
161-
validateField(debouncedField.name);
168+
if (options?.shouldDebounceAndValidate === false || !debouncedField) {
169+
return;
162170
}
171+
172+
validateField(debouncedField.name);
163173
}, [debouncedField]);
164174

165175
useEffect(() => {
@@ -175,6 +185,7 @@ const useAJVForm = <T extends Record<string, any>>(
175185
set: setFormState,
176186
validate: validateForm,
177187
onBlur: handleBlur,
188+
isValid,
178189
isDirty,
179190
state,
180191
};

src/useAjvForm.test.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,45 @@ describe('useAJVForm', () => {
225225
expect(result.current.state.title.error).not.toBe('');
226226
});
227227

228+
it('should not validate and debounce if shouldDebounceAndValidate is set to false', async () => {
229+
const initialData = { title: '' };
230+
const schema: JSONSchemaType<{ title: string }> = {
231+
type: 'object',
232+
required: ['title'],
233+
properties: {
234+
title: { type: 'string', minLength: 3 },
235+
},
236+
};
237+
238+
const { result } = renderHook(() => useAJVForm(initialData, schema));
239+
240+
result.current.set({ title: 'Hi' });
241+
expect(result.current.state.title.error).toBe('');
242+
});
243+
244+
it('should use debounceTime argument for the useDebounce', async () => {
245+
const initialData = { title: '' };
246+
const schema: JSONSchemaType<{ title: string }> = {
247+
type: 'object',
248+
required: ['title'],
249+
properties: {
250+
title: { type: 'string', minLength: 3 },
251+
},
252+
};
253+
254+
const { result } = renderHook(() =>
255+
useAJVForm(initialData, schema, { debounceTime: 1000 }),
256+
);
257+
258+
result.current.set({ title: 'Hi' });
259+
260+
act(() => {
261+
vi.advanceTimersByTime(1000);
262+
});
263+
264+
expect(result.current.state.title.error).not.toBe('');
265+
});
266+
228267
it('validates immediately on blur event', () => {
229268
const initialData = { title: '' };
230269
const schema: JSONSchemaType<{ title: string }> = {
@@ -244,4 +283,54 @@ describe('useAJVForm', () => {
244283
'Should be at least 3 characters long.',
245284
);
246285
});
286+
287+
it('should overwrite the existing error message by providing userDefinedMessages', () => {
288+
const initialData = { title: '' };
289+
const schema: JSONSchemaType<{ title: string }> = {
290+
type: 'object',
291+
required: ['title'],
292+
properties: {
293+
title: { type: 'string', minLength: 3 },
294+
},
295+
};
296+
297+
const { result } = renderHook(() =>
298+
useAJVForm(initialData, schema, {
299+
userDefinedMessages: {
300+
minLength: () => 'Monkey message',
301+
},
302+
}),
303+
);
304+
305+
result.current.set({ title: 'Hi' });
306+
result.current.onBlur('title');
307+
308+
expect(result.current.state.title.error).toBe('Monkey message');
309+
});
310+
311+
it('should correctly update isValid based on form errors', () => {
312+
const initialData = { title: '' };
313+
const schema: JSONSchemaType<{ title: string }> = {
314+
type: 'object',
315+
required: ['title'],
316+
properties: {
317+
title: { type: 'string', minLength: 3 },
318+
},
319+
};
320+
321+
const { result } = renderHook(() => useAJVForm(initialData, schema));
322+
323+
result.current.validate();
324+
325+
expect(result.current.isValid).toBe(false);
326+
327+
result.current.set({ title: 'Hello' });
328+
result.current.onBlur('title');
329+
330+
expect(result.current.isValid).toBe(true);
331+
result.current.set({ title: 'Hi' });
332+
result.current.onBlur('title');
333+
334+
expect(result.current.isValid).toBe(false);
335+
});
247336
});

src/utils/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ export interface UseFormReturn<T> {
5959
reset: () => void;
6060
validate: () => ValidateResult<T>;
6161
isDirty: boolean;
62+
isValid: boolean;
6263
onBlur: (fieldName: string) => void;
6364
}

0 commit comments

Comments
 (0)