127 lines
3.2 KiB
TypeScript
Executable File
127 lines
3.2 KiB
TypeScript
Executable File
import React, { ChangeEvent, useCallback, useState } from "react";
|
|
import { InputBinder } from "./types";
|
|
|
|
type ObjectStateHookConfig = {
|
|
disallowSpaces?: boolean;
|
|
spaceReplacer?: string;
|
|
};
|
|
|
|
export const useObjectState = <T extends object>(initial: T) => {
|
|
const [state, setState] = useState<T>(initial || {} as T);
|
|
|
|
const bindProperty = useCallback(
|
|
<K extends keyof T>(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(<K extends keyof T>(property: K) => ({
|
|
checked: !!state[property],
|
|
name: property,
|
|
onChange: (event: ChangeEvent<HTMLInputElement>) =>
|
|
setState((value) => ({
|
|
...value,
|
|
[event.target.name]: (event.target.checked),
|
|
})),
|
|
readOnly: true,
|
|
}), [state]);
|
|
|
|
const update = useCallback(
|
|
(updates: Partial<T>) => setState((s) => ({ ...s, ...updates })),
|
|
[],
|
|
);
|
|
|
|
const reset = useCallback(() => {
|
|
setState(initial);
|
|
}, [initial]);
|
|
|
|
return {
|
|
bindProperty,
|
|
bindPropertyCheck,
|
|
update,
|
|
state,
|
|
setState,
|
|
reset,
|
|
};
|
|
};
|
|
|
|
export const useObjectStateWrapper = <T extends object>(
|
|
state: T,
|
|
setState: React.Dispatch<React.SetStateAction<T>>,
|
|
) => {
|
|
const bindProperty = useCallback(
|
|
<K extends keyof T>(
|
|
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(<K extends keyof T>(property: K) => ({
|
|
checked: !!state[property],
|
|
name: property,
|
|
onChange: (event: ChangeEvent<HTMLInputElement>) =>
|
|
setState((value) => ({
|
|
...value,
|
|
[event.target.name]: (event.target.checked),
|
|
})),
|
|
readOnly: true,
|
|
}), [setState, state]);
|
|
|
|
const update = useCallback(
|
|
(updates: Partial<T> | ((arg: T) => Partial<T>)) =>
|
|
setState((s) => ({
|
|
...s,
|
|
...(typeof updates === "function" ? updates(s) : updates),
|
|
})),
|
|
[setState],
|
|
);
|
|
|
|
return {
|
|
bindProperty,
|
|
bindPropertyCheck,
|
|
update,
|
|
state,
|
|
setState,
|
|
};
|
|
};
|