Skip to content

Added a custom props option to the useFormState's options #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 29 additions & 28 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ type StateShape<T> = { [key in keyof T]: any };
interface UseFormStateHook {
(
initialState?: Partial<StateShape<any>> | null,
options?: Partial<FormOptions<any>>,
): [FormState<any>, Inputs<any>];
options?: Partial<FormOptions<any, {}>>,
): [FormState<any>, Inputs<any, {}>];

<T extends StateShape<T>, E = StateErrors<T, string>>(
<T extends StateShape<T>, E = StateErrors<T, string>, C = {}>(
initialState?: Partial<T> | null,
options?: Partial<FormOptions<T>>,
): [FormState<T, E>, Inputs<T>];
options?: Partial<FormOptions<T, C>>,
): [FormState<T, E>, Inputs<T, C>];
}

export const useFormState: UseFormStateHook;
Expand All @@ -31,7 +31,7 @@ interface FormState<T, E = StateErrors<T, string>> {
resetField(name: keyof T): void;
}

interface FormOptions<T> {
interface FormOptions<T, C> {
onChange(
event: React.ChangeEvent<InputElement>,
stateValues: StateValues<T>,
Expand All @@ -41,6 +41,7 @@ interface FormOptions<T> {
onClear(): void;
onReset(): void;
onTouched(event: React.FocusEvent<InputElement>): void;
customProps(formState: FormState<T>, name: string): C;
validateOnBlur: boolean;
withIds: boolean | ((name: string, value?: string) => string);
}
Expand All @@ -61,41 +62,41 @@ type StateErrors<T, E = string> = { readonly [A in keyof T]?: E | string };

// Inputs

interface Inputs<T, Name extends keyof T = keyof T> {
interface Inputs<T, C extends {}, Name extends keyof T = keyof T> {
// prettier-ignore
selectMultiple: InputInitializer<T, Args<Name>, Omit<BaseInputProps<T>, 'type'> & MultipleProp>;
select: InputInitializer<T, Args<Name>, Omit<BaseInputProps<T>, 'type'>>;
email: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
color: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
password: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
text: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
textarea: InputInitializer<T, Args<Name>, Omit<BaseInputProps<T>, 'type'>>;
url: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
search: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
number: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
range: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
tel: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
radio: InputInitializer<T, Args<Name, OwnValue>, RadioProps<T>>;
date: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
month: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
week: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
time: InputInitializer<T, Args<Name>, BaseInputProps<T>>;
selectMultiple: InputInitializer<T, Args<Name>, Omit<BaseInputProps<T>, 'type'> & MultipleProp & C>;
select: InputInitializer<T, Args<Name>, Omit<BaseInputProps<T>, 'type'> & C>;
email: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
color: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
password: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
text: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
textarea: InputInitializer<T, Args<Name>, Omit<BaseInputProps<T>, 'type'> & C>;
url: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
search: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
number: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
range: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
tel: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
radio: InputInitializer<T, Args<Name, OwnValue>, RadioProps<T> & C>;
date: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
month: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
week: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
time: InputInitializer<T, Args<Name>, BaseInputProps<T> & C>;
/**
* Checkbox inputs with a value will be treated as a collection of choices.
* Their values in in the form state will be of type Array<string>.
*
* Checkbox inputs without a value will be treated as toggles. Their values in
* in the form state will be of type boolean
*/
checkbox(name: Name, ownValue?: OwnValue): CheckboxProps<T>;
checkbox(options: InputOptions<T, Name, Maybe<OwnValue>>): CheckboxProps<T>;
checkbox(name: Name, ownValue?: OwnValue): CheckboxProps<T> & C;
checkbox(options: InputOptions<T, Name, Maybe<OwnValue>>): CheckboxProps<T> & C;

raw<RawValue, Name extends keyof T = keyof T>(
name: Name,
): RawInputProps<T, Name, RawValue>;
): RawInputProps<T, Name, RawValue> & C;
raw<RawValue, Name extends keyof T = keyof T>(
options: RawInputOptions<T, Name, RawValue>,
): RawInputProps<T, Name, RawValue>;
): RawInputProps<T, Name, RawValue> & C;

label(name: string, value?: string): LabelProps;
id(name: string, value?: string): string;
Expand Down
9 changes: 8 additions & 1 deletion src/useFormState.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const defaultFormOptions = {
onClear: noop,
onReset: noop,
onTouched: noop,
customProps: () => ({}),
withIds: false,
};

Expand Down Expand Up @@ -282,13 +283,19 @@ export default function useFormState(initialState, options) {
...getIdProp('id', name, ownValue),
};

const customProps = formOptions.customProps(formState.current, name);

return isRaw
? {
onChange: inputProps.onChange,
onBlur: inputProps.onBlur,
value: inputProps.value,
...customProps
}
: inputProps;
: {
...inputProps,
...customProps
};
};

const formStateAPI = useRef({
Expand Down
27 changes: 27 additions & 0 deletions test/useFormState-input.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -554,3 +554,30 @@ describe('Input props are memoized', () => {
expect(renderCheck).toHaveBeenCalledTimes(2);
});
});

describe('input type methods return custom props object', () => {
/**
* Supports customProps
*/
it.each([
...InputTypes.textLike,
...InputTypes.numeric,
...InputTypes.time,
'color',
])('returns custom props for type "%s"', type => {
const { result } = renderHook(() => useFormState(null, {
customProps: (formState, name) => ({
isValid: formState.validity[name],
errorMessage: formState.errors[name],
plainString: "plainString",
one: 1
})
}));
expect(result.current[1][type]('input-name')).toEqual(expect.objectContaining({
isValid: undefined,
errorMessage: undefined,
plainString: "plainString",
one: 1
}));
});
});