/** * Gets the values of a form element in object format, based on names. * * @param form Form element to extract values from. * * @example *
* *
* * getFormValues(formRef.current); * // { foo: 'bar' } */ export function getFormValues(form: HTMLFormElement): T { const elementsByName = groupBy( Array.from(form.elements) as NamedElement[], (el) => el.name || '' ); return Object.fromEntries( Object.entries(elementsByName) .filter(([name]) => name) .map(([name, elements]) => [name, getValue(elements)]) ) as T; } const groupBy = (array: T[], keyFn: (item: T, index: number) => string) => array.reduce((obj, item, index) => { const key = keyFn(item, index); (obj[key] ??= []).push(item); return obj; }, {} as Record); function getValue(elements: NamedElement[]) { if (elements.some(isRadio)) return valueOf(elements.find((el) => el.checked)) as string | null; if (elements.length > 1) return elements.map(valueOf).filter(Boolean); const [element] = elements; if (isSelectMultiple(element)) return Array.from(element.options ?? []) .filter((opt) => opt.selected) .map((opt) => opt.value) as string[]; return valueOf(element); } function valueOf(el: NamedElement | undefined) { if (!el) return null; if (isCheckbox(el)) if (!el.hasAttribute('value')) return el.checked; else return el.checked ? el.value : null; if (isRadio(el)) return el.value || ''; if (isNumber(el)) return el.value?.length ? toNumber(el.value) : null; return el.value; } const isCheckbox = (el: NamedElement) => el.type === 'checkbox'; const isNumber = (el: NamedElement) => el.type === 'number'; const isRadio = (el: NamedElement) => el.type === 'radio'; const isSelectMultiple = (el: NamedElement) => el.multiple; const toNumber = (value: string) => (+value === 0 ? 0 : +value || null); // eslint-disable-next-line @typescript-eslint/no-explicit-any type Values = Record; interface NamedElement extends HTMLElement { checked?: boolean; multiple?: boolean; name?: string; options?: HTMLOptionElement[]; type?: string; value?: string; }