FormGroup: add component

This commit is contained in:
Liam Fitzgerald 2021-03-30 13:59:14 +10:00
parent 503dd3538a
commit 7686d55df6
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
2 changed files with 152 additions and 0 deletions

View File

@ -0,0 +1,18 @@
import React from "react";
export type SubmitHandler = () => Promise<any>;
interface IFormGroupContext {
addSubmit: (id: string, submit: SubmitHandler) => void;
onDirty: (id: string, touched: boolean) => void;
onErrors: (id: string, errors: boolean) => void;
submitAll: () => Promise<any>;
}
const fallback: IFormGroupContext = {
addSubmit: () => {},
onDirty: () => {},
onErrors: () => {},
submitAll: () => Promise.resolve(),
};
export const FormGroupContext = React.createContext(fallback);

View File

@ -0,0 +1,134 @@
import React, {
ReactNode,
useEffect,
useCallback,
useState,
useMemo,
} from "react";
import { Button, Box, Row, Col } from "@tlon/indigo-react";
import _ from "lodash";
import { useFormikContext } from "formik";
import { PropFunc } from "~/types";
import { FormGroupContext, SubmitHandler } from "~/logic/lib/formGroup";
import { StatelessAsyncButton } from "./StatelessAsyncButton";
export function useFormGroupContext(id: string) {
const ctx = React.useContext(FormGroupContext);
const addSubmit = useCallback(
(submit: SubmitHandler) => {
ctx.addSubmit(id, submit);
},
[ctx.addSubmit, id]
);
const onDirty = useCallback(
(dirty: boolean) => {
console.log(id, dirty);
ctx.onDirty(id, dirty);
},
[ctx.onDirty, id]
);
const onErrors = useCallback(
(errors: boolean) => {
ctx.onErrors(id, errors);
},
[ctx.onErrors, id]
);
return {
onDirty,
addSubmit,
onErrors,
};
}
export function FormGroupChild(props: { id: string }) {
const { id } = props;
const { addSubmit, onDirty, onErrors } = useFormGroupContext(id);
const { submitForm, dirty, errors } = useFormikContext();
useEffect(() => {
addSubmit(submitForm);
}, [submitForm]);
useEffect(() => {
onDirty(dirty);
}, [dirty, onDirty]);
useEffect(() => {
onErrors(_.keys(_.pickBy(errors, (s) => !!s)).length > 0);
}, [errors, onErrors]);
return <Box display="none" />;
}
export function FormGroup(props: PropFunc<typeof Box>) {
const { children, ...rest } = props;
const [submits, setSubmits] = useState({} as { [id: string]: SubmitHandler });
const [dirty, setDirty] = useState({} as Record<string, boolean>);
const [errors, setErrors] = useState({} as Record<string, boolean>);
const addSubmit = useCallback((id: string, s: SubmitHandler) => {
setSubmits((ss) => ({ ...ss, [id]: s }));
}, []);
const submitAll = useCallback(() => {
return Promise.all(
_.map(
_.pickBy(submits, (_v, k) => dirty[k]),
(f) => f()
)
);
}, [submits, dirty]);
const onDirty = useCallback(
(id: string, t: boolean) => {
setDirty((ts) => ({ ...ts, [id]: t }));
},
[setDirty]
);
const onErrors = useCallback((id: string, e: boolean) => {
setErrors((es) => ({ ...es, [id]: e }));
}, []);
const context = { addSubmit, submitAll, onErrors, onDirty };
const hasErrors = useMemo(
() => _.keys(_.pickBy(errors, (s) => !!s)).length > 0,
[errors]
);
const isDirty = useMemo(
() => _.keys(_.pickBy(dirty, _.identity)).length > 0,
[dirty]
);
return (
<Box {...rest} position="relative">
<FormGroupContext.Provider value={context}>
{children}
</FormGroupContext.Provider>
{isDirty && (
<Row
justifyContent="flex-end"
width="100%"
position="sticky"
bottom="0px"
p="3"
gapX="2"
backgroundColor="white"
borderTop="1"
borderTopColor="washedGray"
>
<Button>Cancel</Button>
<StatelessAsyncButton
onClick={submitAll}
disabled={hasErrors}
primary
>
Update
</StatelessAsyncButton>
</Row>
)}
</Box>
);
}