Merge pull request #11 from Nez27/feat/add-form-component

Add form, dialog component and useForm hooks
This commit is contained in:
Loi Phan
2023-10-27 09:28:19 +07:00
committed by GitHub
7 changed files with 260 additions and 0 deletions
@@ -0,0 +1,22 @@
import { forwardRef } from 'react';
// Components
import { IDialogProps } from '../../globals/interfaces';
// Styled
import { StyledBody, StyledDialog, StyledTitle } from './styled';
const Dialog = forwardRef((props, ref) => {
const { title, children, onClose } = props as IDialogProps;
return (
<StyledDialog
ref={ref as React.LegacyRef<HTMLDialogElement> | undefined}
onClose={onClose}
>
<StyledTitle>{title}</StyledTitle>
<StyledBody>{children}</StyledBody>
</StyledDialog>
);
}) as React.FC<IDialogProps>;
export default Dialog;
@@ -0,0 +1,23 @@
import styled from 'styled-components';
const StyledDialog = styled.dialog`
border-radius: var(--radius-sm);
border-color: var(--border-color);
`;
const StyledTitle = styled.p`
font-size: var(--fs-md);
text-transform: capitalize;
font-weight: 600;
padding: 20px 40px;
border-bottom: 1px solid var(--border-color);
text-align: center;
`;
const StyledBody = styled.div`
padding: 10px 20px;
`;
export { StyledDialog, StyledTitle, StyledBody };
@@ -0,0 +1,19 @@
import { StyledActionBtn, StyledForm } from './styled';
interface IFormProps {
children: JSX.Element | JSX.Element[];
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
id?: string;
}
const Form = ({ children, onSubmit, id }: IFormProps) => {
return (
<StyledForm onSubmit={onSubmit} id={id}>
{children}
</StyledForm>
);
};
Form.Action = StyledActionBtn;
export default Form;
@@ -0,0 +1,16 @@
import styled from 'styled-components';
const StyledForm = styled.form`
display: flex;
flex-direction: column;
gap: 15px;
`;
const StyledActionBtn = styled.div`
display: flex;
justify-content: space-around;
flex-direction: row;
gap: 100px;
`;
export { StyledForm, StyledActionBtn };
@@ -0,0 +1,21 @@
import { Error, Label, StyledFormRow } from './styled';
interface IFormRow {
label: string;
error?: string;
children: JSX.Element | JSX.Element[];
}
const FormRow = ({ label, error, children }: IFormRow) => {
return (
<StyledFormRow>
<Label>{label}</Label>
<div>
{children}
{error && <Error>{error}</Error>}
</div>
</StyledFormRow>
);
};
export default FormRow;
@@ -0,0 +1,21 @@
import styled from 'styled-components';
const StyledFormRow = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
gap: 100px;
`;
const Label = styled.label`
font-size: var(--fs-sm);
text-transform: capitalize;
`;
const Error = styled.p`
color: var(--error-text);
font-size: var(--fs-sm-2x);
margin-top: 5px;
`;
export { StyledFormRow, Label, Error };
+138
View File
@@ -0,0 +1,138 @@
import { useState, useEffect, useCallback, ChangeEvent } from 'react';
// Utils
import {
ERROR,
VALUE,
getPropValues,
isObject,
isRequired,
} from '../helpers/utils';
// Types
import { TKeyValue, TValidator } from '../globals/types';
/**
* Custom hooks to validate your Form...
*
* @param stateSchema model you stateSchema.
* @param stateValidatorSchema model your validation.
* @param submitFormCallback function to be execute during form submission.
* @returns
*/
const useForm = (
stateSchema = {},
stateValidatorSchema = {} as TValidator,
submitFormCallback: (values: TKeyValue) => void,
) => {
const [values, setValues] = useState(getPropValues(stateSchema, VALUE));
const [errors, setErrors] = useState(getPropValues(stateSchema, ERROR));
const [dirty, setDirty] = useState(getPropValues(stateSchema));
const [disable, setDisable] = useState(true);
const [isDirty, setIsDirty] = useState(false);
// Get a local copy of stateSchema
useEffect(() => {
setDisable(true); // Disable button in initial render.
setInitialErrorState();
}, []); // eslint-disable-line
// Validate fields in forms
const validateFormFields = useCallback(
(name: string, value: string) => {
const validator = stateValidatorSchema;
// Making sure that stateValidatorSchema name is same in
// stateSchema
if (!validator[name]) return;
const field = validator[name];
let error = '';
error = isRequired(value, field!.required);
if (isObject(field['validator']) && error === '') {
const fieldValidator = field['validator'];
// Test the function callback if the value is meet the criteria
const testFunc = fieldValidator!['func'];
if (!testFunc!(value)) {
error = fieldValidator!['error']!;
}
}
return error;
},
[stateValidatorSchema],
);
// Set Initial Error State
// When hooks was first rendered...
const setInitialErrorState = useCallback(() => {
Object.keys(errors).map((name) =>
setErrors((prevState) => ({
...prevState,
[name]: validateFormFields(name, values[name] as string),
})),
);
}, [errors, values, validateFormFields]);
// Used to disable submit button if there's a value in errors
// or the required field in state has no value.
// Wrapped in useCallback to cached the function to avoid intensive memory leaked
// in every re-render in component
const validateErrorState = useCallback(
() => Object.values(errors).some((error) => error),
[errors],
);
// For every changed in our state this will be fired
// To be able to disable the button
useEffect(() => {
if (isDirty) {
setDisable(validateErrorState());
}
}, [errors, isDirty, validateErrorState]);
// Event handler for handling changes in input.
const handleOnChange = useCallback(
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setIsDirty(true);
const name = (event.target! as HTMLInputElement).name;
const value = (event.target! as HTMLInputElement).value;
const error = validateFormFields(name, value);
setValues((prevState) => ({ ...prevState, [name]: value }));
setErrors((prevState) => ({ ...prevState, [name]: error }));
setDirty((prevState) => ({ ...prevState, [name]: true }));
},
[validateFormFields],
);
const handleOnSubmit = useCallback(
(event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Making sure that there's no error in the state
// before calling the submit callback function
if (!validateErrorState()) {
submitFormCallback(values);
}
},
[validateErrorState, submitFormCallback, values],
);
return {
handleOnChange,
handleOnSubmit,
values,
errors,
disable,
setValues,
setErrors,
dirty,
};
};
export default useForm;