saleor-dashboard/src/components/Form/Form.tsx

171 lines
4.1 KiB
TypeScript
Raw Normal View History

2019-08-09 10:26:22 +00:00
import React from "react";
2019-06-19 14:40:52 +00:00
import { UserError } from "../../types";
export interface FormProps<T extends {}> {
children: (props: {
data: T;
hasChanged: boolean;
errors: { [key: string]: string };
change(event: React.ChangeEvent<any>, cb?: () => void);
reset();
submit(event?: React.FormEvent<any>);
}) => React.ReactElement<any>;
errors?: UserError[];
initial?: T;
confirmLeave?: boolean;
useForm?: boolean;
resetOnSubmit?: boolean;
onSubmit?(data: T);
}
interface FormComponentProps<T extends {}> extends FormProps<T> {
hasChanged: boolean;
toggleFormChangeState: () => void;
}
interface FormState<T extends {}> {
initial: T;
fields: T;
hasChanged: boolean;
}
class FormComponent<T extends {} = {}> extends React.Component<
FormComponentProps<T>,
FormState<T>
> {
static getDerivedStateFromProps<T extends {} = {}>(
nextProps: FormComponentProps<T>,
prevState: FormState<T>
): FormState<T> {
const changedFields = Object.keys(nextProps.initial).filter(
nextFieldName =>
JSON.stringify(nextProps.initial[nextFieldName]) !==
JSON.stringify(prevState.initial[nextFieldName])
);
if (changedFields.length > 0) {
const swapFields = changedFields.reduce((prev, curr) => {
prev[curr] = nextProps.initial[curr];
return prev;
}, {});
return {
fields: {
...(prevState.fields as any),
...swapFields
},
hasChanged: false,
initial: {
...(prevState.initial as any),
...swapFields
}
};
}
return null;
}
state: FormState<T> = {
fields: this.props.initial,
hasChanged: false,
initial: this.props.initial
};
componentDidUpdate() {
const { hasChanged, confirmLeave, toggleFormChangeState } = this.props;
if (this.state.hasChanged !== hasChanged && confirmLeave) {
toggleFormChangeState();
}
}
componentDidMount() {
const { hasChanged, confirmLeave, toggleFormChangeState } = this.props;
if (this.state.hasChanged !== hasChanged && confirmLeave) {
toggleFormChangeState();
}
}
componentWillUnmount() {
const { hasChanged, confirmLeave, toggleFormChangeState } = this.props;
if (hasChanged && confirmLeave) {
toggleFormChangeState();
}
}
handleChange = (event: React.ChangeEvent<any>, cb?: () => void) => {
const { target } = event;
if (!(target.name in this.state.fields)) {
console.error(`Unknown form field: ${target.name}`);
return;
}
this.setState(
{
fields: {
...(this.state.fields as any),
[target.name]: target.value
},
hasChanged: true
},
typeof cb === "function" ? cb : undefined
);
};
handleKeyDown = (event: React.KeyboardEvent<any>) => {
switch (event.keyCode) {
// Enter
case 13:
this.props.onSubmit(this.state.fields);
break;
}
};
handleSubmit = (event?: React.FormEvent<any>, cb?: () => void) => {
const { resetOnSubmit, onSubmit } = this.props;
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (onSubmit !== undefined) {
onSubmit(this.state.fields);
}
if (cb) {
cb();
}
if (resetOnSubmit) {
this.setState({
fields: this.state.initial
});
}
};
render() {
const { children, errors, useForm = true } = this.props;
const contents = children({
change: this.handleChange,
data: this.state.fields,
errors: errors
? errors.reduce(
(prev, curr) => ({
...prev,
[curr.field.split(":")[0]]: curr.message
}),
{}
)
: {},
hasChanged: this.state.hasChanged,
reset: () =>
this.setState({
fields: this.state.initial
}),
submit: this.handleSubmit
});
return useForm ? (
<form onSubmit={this.handleSubmit}>{contents}</form>
) : (
<div onKeyDown={this.handleKeyDown}>{contents}</div>
);
}
}
export default FormComponent;