Skip to content

Commit c37b955

Browse files
committed
feat(forms): extract and exports "useFormControl" (custom hook) to allow external custom form elements
1 parent 8ac7cb6 commit c37b955

14 files changed

+93
-69
lines changed

demo/demo.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ReactDOM.render(
1616
<ToastsContainer>
1717
<StatefulTabs
1818
vertical={true}
19-
initialTab={4}
19+
initialTab={1}
2020
tabs={[
2121
{
2222
title: 'Dialog',

src/forms/Form.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useRef } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext } from './form-helpers';
4-
import { useForm } from './useForm';
3+
import { FormContext } from './helpers/form-helpers';
4+
import { useForm } from './helpers/useForm';
55
import { FormActions } from './FormActions';
66

77
export function Form({

src/forms/FormAutocomplete.jsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React, { useState, useContext, useRef, useEffect } from 'react';
1+
import React, { useState, useRef, useEffect } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange, normalizeOptions } from './form-helpers';
3+
import { handleInputChange, normalizeOptions } from './helpers/form-helpers';
44
import { Dropdown } from '../mixed/Dropdown';
55
import { useOpenState } from '../utils/useOpenState';
6+
import { useFormControl } from './helpers/useFormControl';
67

78
export function FormAutocomplete({
89
onSearch,
@@ -18,11 +19,11 @@ export function FormAutocomplete({
1819
const [searchValue, setSearchValue] = useState('');
1920
const { isOpen, open, close } = useOpenState();
2021
const [ignoreBlur, setIgnoreBlur] = useState(false);
21-
const formState = useContext(FormContext);
22+
const { getValue, setValue, register } = useFormControl(name);
2223
const inputRef = useRef(null);
2324

2425
useEffect(() => {
25-
formState.register(name, inputRef.current);
26+
register(inputRef.current);
2627
}, []);
2728

2829
return (
@@ -38,8 +39,8 @@ export function FormAutocomplete({
3839
onSearch(value);
3940
open();
4041

41-
if (formState.getValue(name)) {
42-
formState.update(name, null);
42+
if (getValue()) {
43+
setValue(null);
4344
}
4445
},
4546
})}
@@ -67,7 +68,7 @@ export function FormAutocomplete({
6768
isOpen={isOpen()}
6869
items={normalizeOptions(options, FormData).filter(filter(searchValue))}
6970
onSelect={({ value, label }) => {
70-
formState.update(name, value);
71+
setValue(value);
7172
setSearchValue(label);
7273
close();
7374
}}

src/forms/FormCheckbox.jsx

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import React, { useContext, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange } from './form-helpers';
3+
import { useFormControl } from './helpers/useFormControl';
44

55
export function FormCheckbox({ id, name, required, valueLabel }) {
6-
const formState = useContext(FormContext);
7-
const value = formState.getValue(name) || false;
8-
const register = useCallback((ref) => {
9-
formState.register(name, ref);
10-
}, []);
6+
const { getValue, handleOnChange, register } = useFormControl(name, 'boolean');
7+
const registerRef = useCallback(register, []);
118

129
return (
1310
<div className="custom-control custom-checkbox">
1411
<input
1512
{...{ required, name, id }}
1613
type="checkbox"
1714
className="custom-control-input"
18-
onChange={handleInputChange.bind(null, formState)}
19-
checked={value}
20-
ref={register}
15+
onChange={handleOnChange}
16+
value={getValue()}
17+
ref={registerRef}
2118
/>
2219
<label className="custom-control-label" htmlFor={id}>
2320
{valueLabel}

src/forms/FormInput.jsx

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
import React, { useContext, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange } from './form-helpers';
3+
import { useFormControl } from './helpers/useFormControl';
44

55
export function FormInput({ id, type, name, placeholder, required, minLength, maxLength, min, max, pattern, step }) {
6-
const formState = useContext(FormContext);
7-
const register = useCallback((ref) => {
8-
formState.register(name, ref);
9-
}, []);
6+
const { getValue, handleOnChange, register } = useFormControl(name);
7+
const registerRef = useCallback(register, []);
108

119
return (
1210
<input
1311
{...{ required, name, id, placeholder, type, minLength, maxLength, min, max, pattern, step }}
1412
className="form-control"
15-
onChange={handleInputChange.bind(null, formState)}
16-
value={formState.getValue(name) || ''}
17-
ref={register}
13+
onChange={handleOnChange}
14+
value={getValue()}
15+
ref={registerRef}
1816
/>
1917
);
2018
}

src/forms/FormRadio.jsx

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
1-
import React, { useContext, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange } from './form-helpers';
3+
import { useFormControl } from './helpers/useFormControl';
44

55
export function FormRadio({ id, name, required, checkedValue, valueLabel, inline }) {
6-
const formState = useContext(FormContext);
7-
const value = formState.getValue(name) || false;
8-
const register = useCallback((ref) => {
9-
formState.register(name, ref);
10-
}, []);
6+
const { getValue, handleOnChange, register } = useFormControl(name, 'boolean');
7+
const registerRef = useCallback(register, []);
8+
const value = getValue();
119

1210
return (
1311
<div className={`custom-control custom-radio ${inline ? 'custom-control-inline' : ''}`}>
1412
<input
1513
{...{ required, name, id }}
1614
type="radio"
1715
className="custom-control-input"
18-
onChange={handleInputChange.bind(null, formState)}
16+
onChange={handleOnChange}
1917
checked={value === checkedValue}
2018
value={checkedValue}
21-
ref={register}
19+
ref={registerRef}
2220
/>
2321
<label className="custom-control-label" htmlFor={id}>
2422
{valueLabel}

src/forms/FormSelect.jsx

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
import React, { useContext, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange, normalizeOptions } from './form-helpers';
3+
import { normalizeOptions } from './helpers/form-helpers';
4+
import { useFormControl } from './helpers/useFormControl';
45

56
export function FormSelect({ id, name, options, required, placeholder }) {
6-
const formState = useContext(FormContext);
7-
const register = useCallback((ref) => {
8-
formState.register(name, ref);
9-
}, []);
7+
const { getFormData, getValue, handleOnChange, register } = useFormControl(name);
8+
const registerRef = useCallback(register, []);
109

1110
return (
1211
<select
1312
{...{ required, name, id }}
1413
className="custom-select"
15-
onChange={handleInputChange.bind(null, formState)}
16-
value={formState.getValue(name) || ''}
17-
ref={register}
14+
onChange={handleOnChange}
15+
value={getValue()}
16+
ref={registerRef}
1817
>
1918
<option value="">{placeholder}</option>
2019

21-
{renderOptions(options, formState.getFormData())}
20+
{renderOptions(options, getFormData())}
2221
</select>
2322
);
2423
}

src/forms/FormSwitch.jsx

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
import React, { useContext, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange } from './form-helpers';
3+
import { useFormControl } from './helpers/useFormControl';
44

55
export function FormSwitch({ id, name, required, trueLabel, falseLabel }) {
6-
const formState = useContext(FormContext);
7-
const value = formState.getValue(name) || false;
8-
const register = useCallback((ref) => {
9-
formState.register(name, ref);
10-
}, []);
6+
const { getValue, handleOnChange, register } = useFormControl(name, 'boolean');
7+
const registerRef = useCallback(register, []);
8+
const value = getValue();
119

1210
return (
1311
<div className="custom-control custom-switch">
1412
<input
1513
{...{ required, name, id }}
1614
type="checkbox"
1715
className="custom-control-input"
18-
onChange={handleInputChange.bind(null, formState)}
19-
checked={value}
20-
ref={register}
16+
onChange={handleOnChange}
17+
value={value}
18+
ref={registerRef}
2119
/>
2220
<label className="custom-control-label" htmlFor={id}>
2321
{(trueLabel || falseLabel) && (value ? trueLabel : falseLabel)}

src/forms/FormTextarea.jsx

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
import React, { useContext, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange } from './form-helpers';
3+
import { useFormControl } from './helpers/useFormControl';
44

55
export function FormTextarea({ id, name, required, placeholder, rows }) {
6-
const formState = useContext(FormContext);
7-
const register = useCallback((ref) => {
8-
formState.register(name, ref);
9-
}, []);
6+
const { getValue, handleOnChange, register } = useFormControl(name);
7+
const registerRef = useCallback(register, []);
108

119
return (
1210
<textarea
1311
{...{ required, name, id, placeholder, rows }}
1412
className="form-control"
15-
onChange={handleInputChange.bind(null, formState)}
16-
value={formState.getValue(name) || ''}
17-
ref={register}
13+
onChange={handleOnChange}
14+
value={getValue()}
15+
ref={registerRef}
1816
></textarea>
1917
);
2018
}

src/forms/FormValidationFeedback.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useContext } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext } from './form-helpers';
3+
import { FormContext } from './helpers/form-helpers';
44

55
export function FormValidationFeedback({ name, mockInvalidSibling }) {
66
const formState = useContext(FormContext);
File renamed without changes.

src/forms/useForm.js renamed to src/forms/helpers/useForm.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from 'react';
2-
import { useArrayValueMap } from '../utils/useValueMap';
32
import { validateFormElement } from './form-helpers';
3+
import { useArrayValueMap } from '../../utils/useValueMap';
44

55
export function useForm(initialState, validations) {
66
const [formState, setFormState] = useState(initialState);

src/forms/helpers/useFormControl.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useContext } from 'react';
2+
import { FormContext } from './form-helpers';
3+
4+
export function useFormControl(name, type) {
5+
const formState = useContext(FormContext);
6+
7+
function setValue(value) {
8+
formState.update(name, value);
9+
}
10+
11+
return {
12+
getValue: () => formState.getValue(name) || getEmptyValue(type),
13+
setValue,
14+
handleOnChange: ({ target }) => {
15+
const value = target.type === 'checkbox' ? target.checked : target.value;
16+
17+
setValue(value);
18+
},
19+
register: (ref) => {
20+
formState.register(name, ref);
21+
},
22+
getFormData: () => formState.getFormData(),
23+
};
24+
}
25+
26+
function getEmptyValue(type) {
27+
switch (type) {
28+
case 'boolean':
29+
return false;
30+
31+
default:
32+
return '';
33+
}
34+
}

src/forms/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './FormRadio';
77
export * from './FormSelect';
88
export * from './FormSwitch';
99
export * from './FormTextarea';
10+
export * from './helpers/useFormControl';

0 commit comments

Comments
 (0)