import React, { ChangeEvent, useCallback, useState } from "react"; import { InputBinder } from "../types/inputBinder"; type ObjectStateHookConfig = { disallowSpaces?: boolean; spaceReplacer?: string; }; export const useObjectState = (initial: T) => { const [state, setState] = useState(initial || {} as T); const bindProperty = useCallback( (property: K, config?: ObjectStateHookConfig) => ({ value: state[property] ?? "", name: property, onChange: ( event: ChangeEvent< HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement >, ) => setState((value) => ( { ...value, [event.target.name]: ( (typeof value[property] === "number") ? Number(event.target.value) || 0 : config?.disallowSpaces ? event.target.value.replace(" ", config.spaceReplacer || "_") : event.target.value ), } )), }), [state], ); const bindPropertyCheck = useCallback((property: K) => ({ checked: !!state[property], name: property, onChange: (event: ChangeEvent) => setState((value) => ({ ...value, [event.target.name]: (event.target.checked), })), readOnly: true, }), [state]); const update = useCallback( (updates: Partial) => setState((s) => ({ ...s, ...updates })), [], ); const reset = useCallback(() => { setState(initial); }, [initial]); return { bindProperty, bindPropertyCheck, update, state, setState, reset, }; }; export const useObjectStateWrapper = ( state: T, setState: React.Dispatch>, ) => { const bindProperty = useCallback( ( property: K, config?: ObjectStateHookConfig, ): InputBinder => ({ value: state[property]?.toString() ?? "", name: property.toString(), onChange: ( event: ChangeEvent< HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement >, ) => setState((value) => ( { ...value, [event.target.name]: ( (typeof value[property] === "number") ? Number(event.target.value) || 0 : config?.disallowSpaces ? event.target.value.replace(" ", config.spaceReplacer || "_") : event.target.value ), } )), }), [setState, state], ); const bindPropertyCheck = useCallback((property: K) => ({ checked: !!state[property], name: property, onChange: (event: ChangeEvent) => setState((value) => ({ ...value, [event.target.name]: (event.target.checked), })), readOnly: true, }), [setState, state]); const update = useCallback( (updates: Partial | ((arg: T) => Partial)) => setState((s) => ({ ...s, ...(typeof updates === "function" ? updates(s) : updates), })), [setState], ); return { bindProperty, bindPropertyCheck, update, state, setState, }; };