import { ChangeEvent , Reducer , useEffect , useMemo , useReducer } from "react" ; import { reduceArrayMeta } from "../../util/arrays.util" ; import { generateId , reducer } from "./constants" ; import { Action , BaseFieldArrayOptions , FieldArrayActions , State , FieldArrayData , FieldArrayMeta , } from "./types" ; export function useBaseFieldArray < T , E = HTMLInputElement > ( options ) { const { checkForErrors , extractValue , checkIfEmpty , initialValue , isRequired , defaultValue , } = options ; const initialValuesJson = JSON . stringify ( initialValue ) ; const { initialState , initialValues } = useMemo ( ( ) => { const initialState = Array . isArray ( initialValue ) ? initialValue . map ( ( val ) => ( { wasTouched : false , isFocussed : false , error : undefined , value : val , id : generateId ( ) } ) ) : [ ] ; const initialValues = initialState . map ( x => x . value ) ; return { initialState , initialValues } ; } , [ initialValuesJson ] ) ; const newItemValue = Array . isArray ( initialValue ) ? defaultValue : initialValue ; const [ state , dispatch ] = useReducer < Reducer < State < T > , Action < T > > > ( reducer ( newItemValue ) , initialState ) ; useEffect ( ( ) => { dispatch ( { type : "RESET_ALL" , values : initialValues , } ) ; } , [ JSON . stringify ( initialValues ) ] ) ; const required = Boolean ( isRequired ) ; const appendItem = ( ) => { dispatch ( { type : "APPEND_ITEM" } ) ; } ; const onChange = ( e , index ) => { const _value = extractValue ( e . target ) ; const meta = items [ index ] . meta ; const _error = checkForErrors ( { ... meta , value : _value , } , items . map ( ( item ) => item . meta ) ) ; dispatch ( { type : "UPDATE_ITEM" , index , updates : { error : _error , value : _value } , } ) ; } ; const onBlur = ( index ) => { const meta = items [ index ] . meta ; const _error = checkForErrors ( { ... meta , wasTouched : true , isFocussed : false } , items . map ( ( item ) => item . meta ) ) ; dispatch ( { type : "UPDATE_ITEM" , index , updates : { isFocussed : false , wasTouched : true , error : _error } , } ) ; } ; const onFocus = ( index ) => { const meta = items [ index ] . meta ; const _error = checkForErrors ( { ... meta , isFocussed : true } , items . map ( ( item ) => item . meta ) ) ; dispatch ( { type : "UPDATE_ITEM" , index , updates : { isFocussed : true , wasTouched : true , error : _error } , } ) ; } ; const reset = ( index , toValue ? ) => { dispatch ( { type : "RESET" , index , value : toValue ?? initialState [ index ] ?. value , } ) ; } ; const resetAll = ( toValues ? ) => { dispatch ( { type : "RESET_ALL" , values : toValues ?? initialState . map ( ( s ) => s . value ) , } ) ; } ; const insertItem = ( index , value ? ) => { dispatch ( { type : "INSERT_ITEM" , index , data : value != null ? { value } : undefined } ) ; } ; const removeItem = ( index ) => { dispatch ( { type : "REMOVE_ITEM" , index , } ) ; } ; const validate = ( ) => { const allItemMeta = items . map ( item => item . meta ) ; const errorUpdates = items . map ( item => { const error = checkForErrors ( item . meta , allItemMeta ) ; return { error } ; } ) ; dispatch ( { type : "UPDATE_ITEMS" , updates : errorUpdates } ) ; } ; const arrayActions = { removeItem , resetAll , insertItem , appendItem , validate } ; const items = state . map ( ( item , i ) => ( { props : { onBlur : ( ) => onBlur ( i ) , onChange : ( e ) => onChange ( e , i ) , onFocus : ( ) => onFocus ( i ) , value : item . value , required , key : item . id , } , meta : { error : item . error , wasTouched : item . wasTouched , value : item . value , isFocussed : item . isFocussed , hasError : item . error != null , isDirty : initialState [ i ] ?. value !== item . value , isEmpty : checkIfEmpty ( item . value ) , isRequired : required , } , actions : { remove : ( ) => dispatch ( { type : "REMOVE_ITEM" , index : i , } ) , setValue : ( value ? ) => dispatch ( { type : "UPDATE_ITEM" , index : i , updates : { value } , } ) , setError : ( error ? ) => dispatch ( { type : "UPDATE_ITEM" , index : i , updates : { error } , } ) , setWasTouched : ( touched ? ) => dispatch ( { type : "UPDATE_ITEM" , index : i , updates : { wasTouched : touched } , } ) , setIsFocussed : ( focussed ? ) => dispatch ( { type : "UPDATE_ITEM" , index : i , updates : { isFocussed : focussed } , } ) , reset : ( ) => reset ( i ) , } , } ) ) ; const arrayMeta = reduceArrayMeta ( items , initialValues ) ; return [ items , arrayMeta , arrayActions ] ; }