javascript
import React, { useReducer, useCallback } from 'react';
const initialState = {
values: { profile: { name: '', email: '' }, address: { city: '' } },
errors: {},
touched: {},
validating: {},
submitting: false,
submitError: null
};
function formReducer(state, action) {
switch(action.type) {
case 'FIELD_CHANGE':
return {
...state,
values: setIn(state.values, action.path, action.value),
errors: unsetIn(state.errors, action.path),
};
case 'FIELD_BLUR':
return { ...state, touched: setIn(state.touched, action.path, true) };
case 'SYNC_VALIDATE':
return { ...state, errors: { ...state.errors, ...action.errors } };
case 'ASYNC_VALIDATE_START':
return { ...state, validating: setIn(state.validating, action.path, true) };
case 'ASYNC_VALIDATE_END':
return {
...state,
validating: setIn(state.validating, action.path, false),
errors: setIn(state.errors, action.path, action.error || null)
};
case 'SUBMIT_START':
return { ...state, submitting: true, submitError: null };
case 'SUBMIT_END':
return { ...state, submitting: false, submitError: action.error || null };
default: return state;
}
}
// Helper utilities setIn/unsetIn omitted for brevity
export function useForm() {
const [state, dispatch] = useReducer(formReducer, initialState);
const change = useCallback((path, value) => {
dispatch({ type: 'FIELD_CHANGE', path, value });
// run sync validation
const syncErr = runSyncValidators(path, value, state.values);
dispatch({ type: 'SYNC_VALIDATE', errors: syncErr });
}, [state.values]);
const blur = useCallback((path) => dispatch({ type: 'FIELD_BLUR', path }), []);
const asyncValidate = useCallback(async (path, validator) => {
dispatch({ type: 'ASYNC_VALIDATE_START', path });
const error = await validator(getIn(state.values, path));
dispatch({ type: 'ASYNC_VALIDATE_END', path, error });
}, [state.values]);
const submit = useCallback(async (onSubmit) => {
dispatch({ type: 'SUBMIT_START' });
try {
await onSubmit(state.values);
dispatch({ type: 'SUBMIT_END' });
} catch (e) {
dispatch({ type: 'SUBMIT_END', error: e.message || 'Submit failed' });
}
}, [state.values]);
return { state, change, blur, asyncValidate, submit, dispatch };
}