Files
payload/src/client/components/forms/Form/index.js
2020-04-02 15:59:27 -04:00

188 lines
4.4 KiB
JavaScript

import React, { useContext, useState, useReducer } from 'react';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
import { unflatten } from 'flatley';
import FormContext from './Context';
import { useLocale } from '../../utilities/Locale';
import { useStatusList } from '../../modules/Status';
import HiddenInput from '../field-types/HiddenInput';
import { requests } from '../../../api';
import useThrottledEffect from '../../../hooks/useThrottledEffect';
import { useUser } from '../../data/User';
import fieldReducer from './reducer';
import './index.scss';
const baseClass = 'form';
export const useForm = () => useContext(FormContext);
const Form = (props) => {
const [fields, dispatchFields] = useReducer(fieldReducer, {});
const [submitted, setSubmitted] = useState(false);
const [processing, setProcessing] = useState(false);
const history = useHistory();
const locale = useLocale();
const { addStatus } = useStatusList();
const { refreshToken } = useUser();
const {
onSubmit,
ajax,
method,
action,
handleAjaxResponse,
children,
className,
redirect,
disableSuccessStatus,
} = props;
const submit = (e) => {
setSubmitted(true);
let isValid = true;
Object.keys(fields).forEach((field) => {
if (!fields[field].valid) {
isValid = false;
}
});
// If not valid, prevent submission
if (!isValid) {
e.preventDefault();
// If submit handler comes through via props, run that
} else if (onSubmit) {
e.preventDefault();
onSubmit(fields);
// If form is AJAX, fetch data
} else if (ajax !== false) {
e.preventDefault();
const data = {};
// Clean up data passed from field state
Object.keys(fields).forEach((key) => {
data[key] = fields[key].value;
});
setProcessing(true);
// Make the API call from the action
requests[method.toLowerCase()](action, {
body: JSON.stringify(unflatten(data)),
headers: {
'Content-Type': 'application/json',
},
}).then(
(res) => {
if (res.status < 400) {
// If prop handleAjaxResponse is passed, pass it the response
if (handleAjaxResponse && typeof handleAjaxResponse === 'function') {
return handleAjaxResponse(res);
}
if (redirect) {
return history.push(redirect, data);
}
setProcessing(false);
res.json().then((json) => {
if (!disableSuccessStatus) {
addStatus({
message: json.message,
type: 'success',
});
}
});
} else {
res.json().then((json) => {
setProcessing(false);
json.errors.forEach((err) => {
addStatus({
message: err.message,
type: 'error',
});
});
});
}
},
(error) => {
setProcessing(false);
addStatus({
message: error.message,
type: 'error',
});
},
);
}
// If valid and not AJAX submit as usual
};
useThrottledEffect(() => {
refreshToken();
}, 15000, [fields]);
const classes = [
className,
baseClass,
].filter(Boolean).join(' ');
return (
<form
noValidate
onSubmit={submit}
method={method}
action={action}
className={classes}
>
<FormContext.Provider value={{
dispatchFields,
fields,
processing,
submitted,
}}
>
<HiddenInput
name="locale"
defaultValue={locale}
/>
{children}
</FormContext.Provider>
</form>
);
};
Form.defaultProps = {
redirect: '',
onSubmit: null,
ajax: true,
method: 'POST',
action: '',
handleAjaxResponse: null,
className: '',
disableSuccessStatus: false,
};
Form.propTypes = {
disableSuccessStatus: PropTypes.bool,
onSubmit: PropTypes.func,
ajax: PropTypes.bool,
method: PropTypes.oneOf(['post', 'POST', 'get', 'GET', 'put', 'PUT', 'delete', 'DELETE']),
action: PropTypes.string,
handleAjaxResponse: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
className: PropTypes.string,
redirect: PropTypes.string,
};
export default Form;