Skip to content

Commit c4fc270

Browse files
committed
Bump version to 1.0.25; add isRequired property to form state and update validation logic
1 parent 854e3de commit c4fc270

File tree

5 files changed

+297
-150
lines changed

5 files changed

+297
-150
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@programmer_network/use-ajv-form",
3-
"version": "1.0.24",
3+
"version": "1.0.25",
44
"description": "Custom React Hook that integrates with Ajv JSON Schema Validator",
55
"main": "dist/use-ajv-form.es.js",
66
"author": "Aleksandar Grbic",

src/index.ts

Lines changed: 101 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { useEffect, useMemo, useRef, useState } from 'react';
2-
import { addUserDefinedKeywords, getErrors, getInitial, getValue } from './utils';
2+
import {
3+
addUserDefinedKeywords,
4+
getErrors,
5+
getFormState,
6+
getInitial,
7+
getValue,
8+
} from './utils';
39
import { ajv as ajvInternal } from './utils/validation';
410

511
import { ErrorObject, JSONSchemaType, KeywordDefinition, SchemaObject } from 'ajv';
612
import { useDebounce } from './Hooks/useDebounce';
7-
import {
8-
AJVMessageFunction,
9-
FormField,
10-
IState,
11-
UseFormReturn,
12-
useFormErrors,
13-
} from './utils/types';
13+
import { AJVMessageFunction, FormField, IState, UseFormReturn } from './utils/types';
1414
import Logger from './utils/Logger';
1515
const useAJVForm = <T extends Record<string, any>>(
1616
initial: T,
@@ -26,20 +26,44 @@ const useAJVForm = <T extends Record<string, any>>(
2626
},
2727
): UseFormReturn<T> => {
2828
const ajvInstance = options?.ajv || ajvInternal;
29-
const initialStateRef = useRef<IState<T>>(getInitial(initial));
3029

31-
const [state, setState] = useState<IState<T>>(getInitial(initial));
30+
const initialStateRef = useRef<IState<T>>(
31+
getInitial(initial, schema) as IState<T>,
32+
);
33+
34+
const logger = useMemo(
35+
() => new Logger(options?.debug || false),
36+
[options?.debug],
37+
);
38+
39+
// Precompute field dependencies
40+
const fieldDependencies = useMemo(() => {
41+
const dependencies: Record<string, string[]> = {};
42+
if (schema.allOf) {
43+
schema.allOf.forEach((condition: any) => {
44+
if (condition.if) {
45+
const parentField = Object.keys(condition.if.properties || {})[0];
46+
const dependentFields = condition.then?.required || [];
47+
dependencies[parentField] = [
48+
...(dependencies[parentField] || []),
49+
...dependentFields,
50+
];
51+
}
52+
});
53+
}
54+
55+
logger.log('Precomputed field dependencies:', dependencies);
56+
return dependencies;
57+
}, [schema]);
58+
59+
const [state, setState] = useState<IState<T>>(initialStateRef.current);
3260

3361
const [currentField, setCurrentField] = useState<{
3462
name: keyof T;
3563
editId: number;
3664
} | null>(null);
37-
const [editCounter, setEditCounter] = useState(0);
65+
3866
const debouncedField = useDebounce(currentField, options?.debounceTime || 1000);
39-
const logger = useMemo(
40-
() => new Logger(options?.debug || false),
41-
[options?.debug],
42-
);
4367

4468
if (options?.customKeywords?.length) {
4569
addUserDefinedKeywords(ajvInstance, options.customKeywords);
@@ -49,13 +73,11 @@ const useAJVForm = <T extends Record<string, any>>(
4973

5074
const resetForm = () => {
5175
logger.log('Form reset to initial state');
52-
5376
setState(initialStateRef.current);
5477
};
5578

5679
const validateField = (fieldName: keyof T) => {
5780
const fieldData = { [fieldName]: state[fieldName].value };
58-
5981
const isValid = AJVValidate(fieldData);
6082
const errors = AJVValidate.errors || [];
6183

@@ -70,7 +92,6 @@ const useAJVForm = <T extends Record<string, any>>(
7092
: getErrors(errors, options?.userDefinedMessages, logger, schema);
7193

7294
const error = fieldErrors[fieldName as string] || '';
73-
7495
setState((prevState) => ({
7596
...prevState,
7697
[fieldName]: {
@@ -82,174 +103,113 @@ const useAJVForm = <T extends Record<string, any>>(
82103

83104
const handleBlur = (fieldName: keyof T) => {
84105
logger.log(`Field '${String(fieldName)}' blurred`);
85-
86106
validateField(fieldName);
87107
};
88108

89109
const setFormState = (form: Partial<FormField<T>>) => {
90-
setState((current) => {
91-
const newState = { ...current };
92-
93-
Object.keys(form).forEach((key) => {
94-
const name = key as keyof T;
95-
newState[name] = {
96-
...newState[name],
97-
value: getValue(form[name]),
98-
error: newState[name]?.error || '',
99-
};
100-
});
101-
102-
setCurrentField({
103-
name: Object.keys(form)[0] as keyof T,
104-
editId: editCounter,
105-
});
106-
107-
setEditCounter(editCounter + 1);
108-
109-
return newState;
110+
setState(getFormState(state, form, fieldDependencies, schema) as IState<T>);
111+
setCurrentField({
112+
name: Object.keys(form)[0] as keyof T,
113+
editId: currentField?.editId || 0 + 1,
110114
});
111115
};
112116

113-
const _setErrors = (errors: useFormErrors<T>) => {
114-
return Object.keys(errors).reduce(
115-
(acc, fieldName) => {
116-
const key = fieldName as keyof typeof state;
117-
118-
return {
119-
...acc,
120-
[fieldName]: {
121-
value: getValue(state[key].value),
122-
error: errors[fieldName] || '',
123-
},
124-
};
125-
},
126-
{ ...state },
127-
);
128-
};
129-
130117
const validateForm = () => {
131-
try {
132-
const data = Object.keys(state).reduce((acc, inputName) => {
133-
return {
134-
...acc,
135-
[inputName]: getValue(state[inputName].value),
136-
};
137-
}, {} as T);
138-
139-
logger.log('Validating entire form:', { data });
140-
141-
const isValid = AJVValidate(data);
142-
if (!isValid && AJVValidate.errors) {
143-
logger.error('Form validation failed with errors:', AJVValidate.errors);
144-
145-
const errors: useFormErrors<T> = getErrors(
146-
AJVValidate.errors,
147-
options?.userDefinedMessages,
148-
logger,
149-
);
150-
151-
setState((currentState) =>
152-
Object.keys(currentState).reduce((acc, inputName) => {
153-
const currentField = currentState[inputName as keyof T];
154-
155-
return {
156-
...acc,
157-
[inputName]: {
158-
...currentField,
159-
error: errors[inputName] || '',
160-
},
161-
};
162-
}, {} as IState<T>),
163-
);
118+
const data = Object.keys(state).reduce((acc, inputName) => {
119+
acc[inputName as keyof T] = getValue(state[inputName].value) as T[keyof T];
120+
return acc;
121+
}, {} as T);
164122

165-
return { isValid: false, data: null };
166-
}
123+
logger.log('Validating entire form:', { data });
124+
const isValid = AJVValidate(data);
167125

168-
setState((currentState) =>
169-
Object.keys(currentState).reduce((acc, inputName) => {
170-
const currentField = currentState[inputName as keyof T];
126+
if (!isValid && AJVValidate.errors) {
127+
logger.error('Form validation failed with errors:', AJVValidate.errors);
171128

129+
const errors = getErrors(
130+
AJVValidate.errors,
131+
options?.userDefinedMessages,
132+
logger,
133+
);
134+
setState((prevState) =>
135+
Object.keys(prevState).reduce((updatedState, fieldName) => {
172136
return {
173-
...acc,
174-
[inputName]: {
175-
...currentField,
176-
error: '',
137+
...updatedState,
138+
[fieldName]: {
139+
...prevState[fieldName],
140+
error: errors[fieldName] || '',
177141
},
178142
};
179143
}, {} as IState<T>),
180144
);
181145

182-
logger.log('Form is valid:', data);
183-
184-
return { isValid: true, data };
185-
} catch (error) {
186-
logger.error('Unexpected error during form validation:', error);
187-
188146
return { isValid: false, data: null };
189147
}
190-
};
191148

192-
const isFormDirty = (
193-
currentState: IState<T>,
194-
initialState: IState<T>,
195-
): boolean => {
196-
return Object.keys(currentState).some(
197-
(key) => currentState[key].value !== initialState[key].value,
198-
);
199-
};
200-
201-
const isFormValid = (currentState: IState<T>): boolean => {
202-
const hasErrors = Object.keys(currentState).some(
203-
(key) => currentState[key].error !== '',
149+
// Clear errors if valid
150+
setState((prevState) =>
151+
Object.keys(prevState).reduce((updatedState, fieldName) => {
152+
return {
153+
...updatedState,
154+
[fieldName]: {
155+
...prevState[fieldName],
156+
error: '',
157+
},
158+
};
159+
}, {} as IState<T>),
204160
);
205161

206-
return !hasErrors;
162+
logger.log('Form is valid:', data);
163+
return { isValid: true, data };
207164
};
208165

209-
const isValid = useMemo(() => {
210-
return isFormValid(state);
166+
const isDirty = useMemo(() => {
167+
return Object.keys(state).some(
168+
(key) => state[key].value !== initialStateRef.current[key].value,
169+
);
211170
}, [state]);
212171

213-
const isDirty = useMemo(
214-
() => isFormDirty(state, initialStateRef.current),
172+
const isValid = useMemo(
173+
() => Object.keys(state).every((key) => !state[key].error),
215174
[state],
216175
);
217176

218-
const setErrors = (errors: ErrorObject[]): void => {
219-
setState(
220-
_setErrors(getErrors(errors, options?.userDefinedMessages, logger, schema)),
177+
const setErrors = (errors: ErrorObject[]) => {
178+
const newErrors = getErrors(
179+
errors,
180+
options?.userDefinedMessages,
181+
logger,
182+
schema,
183+
);
184+
setState((prevState) =>
185+
Object.keys(newErrors).reduce((updatedState, fieldName) => {
186+
return {
187+
...updatedState,
188+
[fieldName]: {
189+
...prevState[fieldName],
190+
error: newErrors[fieldName] || '',
191+
},
192+
};
193+
}, {} as IState<T>),
221194
);
222195
};
223196

224197
useEffect(() => {
225198
if (
226-
options?.shouldDebounceAndValidate === false ||
227199
!debouncedField ||
228-
!isDirty
200+
!isDirty ||
201+
options?.shouldDebounceAndValidate === false
229202
) {
230203
return;
231204
}
232205
validateField(debouncedField.name);
233206
}, [debouncedField, isDirty]);
234207

235-
useEffect(() => {
236-
if (!options?.errors?.length) {
237-
return;
238-
}
239-
240-
setState(
241-
_setErrors(
242-
getErrors(options?.errors, options?.userDefinedMessages, logger, schema),
243-
),
244-
);
245-
}, [options?.errors]);
246-
247208
return {
248209
reset: resetForm,
249210
set: setFormState,
250211
setErrors,
251212
validate: validateForm,
252-
onBlur: handleBlur,
253213
isValid,
254214
isDirty,
255215
data: Object.keys(state).reduce((acc, fieldName) => {
@@ -259,6 +219,7 @@ const useAJVForm = <T extends Record<string, any>>(
259219
};
260220
}, {} as T),
261221
state,
222+
onBlur: handleBlur,
262223
};
263224
};
264225

0 commit comments

Comments
 (0)