Skip to content

Commit df93db8

Browse files
committed
feat: save generator form state in sessionStore
* simplify formik state handling and add custom useFormik hook.
1 parent 5c96738 commit df93db8

File tree

5 files changed

+553
-459
lines changed

5 files changed

+553
-459
lines changed

src/components/VerboseTextFieldArray.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
TextFieldProps,
2828
Typography,
2929
} from "@mui/material";
30-
import { VerboseFieldState } from "../lib/formValidationTypes";
30+
import { FormFieldState, VerboseFieldState } from "../lib/formValidationTypes";
3131
import FieldNameLabel from "./FieldNameLabel";
3232
import NecessityLabel, { Necessity } from "./NecessityLabel";
3333
import VerboseHelperText from "./VerboseHelperText";
@@ -40,9 +40,8 @@ export type VerboseFieldArrayRenderProps = TextFieldProps & {
4040
componentFieldName?: string;
4141
description: string | string[];
4242
values: string[];
43-
state?: VerboseFieldState | undefined;
43+
state?: FormFieldState<VerboseFieldState | undefined>;
4444
necessity?: Necessity;
45-
childStates?: (VerboseFieldState | undefined)[];
4645
allowEmpty?: boolean;
4746
pushItem(obj: unknown): void;
4847
removeItem(index: number): void;
@@ -64,7 +63,6 @@ export default function VerboseTextFieldArray(
6463
values,
6564
state = undefined,
6665
necessity = undefined,
67-
childStates = undefined,
6866
allowEmpty = false,
6967

7068
pushItem,
@@ -95,7 +93,7 @@ export default function VerboseTextFieldArray(
9593
{necessity && <NecessityLabel necessity={necessity} />}
9694
</Grid>
9795

98-
<VerboseHelperText state={state} />
96+
<VerboseHelperText state={state?.state} />
9997

10098
{/* Field name label */}
10199
<Grid container item spacing={1}>
@@ -136,8 +134,8 @@ export default function VerboseTextFieldArray(
136134
name={`${componentName}.${index}`}
137135
label={rowLabel}
138136
state={
139-
Array.isArray(childStates)
140-
? childStates[index]
137+
Array.isArray(state?.childStates)
138+
? state?.childStates[index]
141139
: undefined
142140
}
143141
value={value}
@@ -182,7 +180,6 @@ export default function VerboseTextFieldArray(
182180
VerboseTextFieldArray.defaultProps = {
183181
state: undefined,
184182
necessity: undefined,
185-
childStates: undefined,
186183
componentFieldName: undefined,
187184
addRowLabel: "Add row",
188185
allowEmpty: false,

src/components/useVerboseFormik.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//
2+
// Copyright 2022 Inrupt Inc.
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal in
6+
// the Software without restriction, including without limitation the rights to use,
7+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8+
// Software, and to permit persons to whom the Software is furnished to do so,
9+
// subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
//
21+
22+
import { FormikValues, useFormik } from "formik";
23+
import {
24+
FormFieldStates,
25+
useFieldStates,
26+
VerboseFieldState,
27+
} from "../lib/formValidationTypes";
28+
29+
/**
30+
* Extends the {@link useFormik} function by adding state property supporting
31+
* verbose field state management.
32+
*
33+
* @param initialValues The initial values for the form fields.
34+
* @param initialStates The initial states of the form fields.
35+
*
36+
* @returns formik object
37+
*/
38+
export default function useVerboseFormik<FormParameters extends FormikValues>({
39+
initialValues,
40+
initialStates,
41+
}: {
42+
initialValues: FormParameters;
43+
initialStates: FormFieldStates<FormParameters, VerboseFieldState | undefined>;
44+
}) {
45+
const states = useFieldStates<FormParameters, VerboseFieldState | undefined>(
46+
initialStates
47+
);
48+
49+
const formik = useFormik<FormParameters>({
50+
initialValues,
51+
onSubmit: () => {},
52+
validateOnBlur: false,
53+
validateOnChange: false,
54+
});
55+
56+
const modifiedFormik: Omit<
57+
typeof formik,
58+
"errors" | "setErrors" | "initialErrors" | "setFieldErrors"
59+
> & {
60+
states: typeof states;
61+
} = { ...formik, states };
62+
63+
// Helper to add an array field's child value.
64+
const addChild = async (fieldName: keyof FormParameters, value: unknown) => {
65+
await formik.setValues({
66+
...formik.values,
67+
[fieldName]: [...formik.values[fieldName], value],
68+
});
69+
states.setChildren(fieldName, [
70+
...(states.all[fieldName].childStates || []),
71+
undefined,
72+
]);
73+
};
74+
75+
// Helper to remove an array field's child value.
76+
const removeChild = async (
77+
fieldName: keyof FormParameters,
78+
index: number
79+
) => {
80+
const values = formik.values[fieldName];
81+
if (!Array.isArray(values)) {
82+
throw new Error(
83+
"Cannot remove child form value from an object that's not an array."
84+
);
85+
}
86+
await formik.setValues({
87+
...formik.values,
88+
[fieldName]: values.filter((_: unknown, i: number) => i !== index),
89+
});
90+
states.setChildren(
91+
fieldName,
92+
states.all[fieldName].childStates?.filter((_, i) => i !== index) || []
93+
);
94+
};
95+
96+
return { ...modifiedFormik, addChild, removeChild };
97+
}

src/lib/formValidationTypes.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@
2121

2222
import { useState } from "react";
2323

24+
export declare type FormFieldState<State> = {
25+
state?: State;
26+
childStates?: State[];
27+
};
28+
2429
/**
25-
* Provides a type to store states of type State of a form
26-
* with field values of type `Value`.
30+
* Provides a type to store states of type `State` of a form
31+
* with the value keys of type `Value`. Additionally, an array
32+
* `childStates` is provided for nested components.
2733
* Inspired by `FormikErrors<Values>` type.
2834
*/
2935
export declare type FormFieldStates<Values, State> = {
30-
[K in keyof Values]: { state?: State; childStates?: State[] };
36+
[K in keyof Values]: FormFieldState<State>;
3137
};
3238

3339
/**
@@ -41,20 +47,22 @@ export declare type FormFieldHelperTexts<Values> = FormFieldStates<
4147
>;
4248

4349
/**
44-
* Helper function to manage field values of verbose forms.
50+
* Helper function to manage validation state of forms.
4551
*
4652
* @param initialValues
4753
* @returns [states, setStates, setFieldState, setArrayFieldState]
4854
*/
4955
export function useFieldStates<IFormParameters, V>(
5056
initialValues: FormFieldStates<IFormParameters, V>
51-
): [
52-
FormFieldStates<IFormParameters, V>,
53-
React.Dispatch<React.SetStateAction<FormFieldStates<IFormParameters, V>>>,
54-
(fieldName: keyof IFormParameters, value: V) => void,
55-
(fieldName: keyof IFormParameters, value: V, index: number) => void,
56-
(fieldName: keyof IFormParameters, value: V[]) => void
57-
] {
57+
): {
58+
all: FormFieldStates<IFormParameters, V>;
59+
setAll: React.Dispatch<
60+
React.SetStateAction<FormFieldStates<IFormParameters, V>>
61+
>;
62+
setFor: (fieldName: keyof IFormParameters, value: V) => void;
63+
setChild: (fieldName: keyof IFormParameters, value: V, index: number) => void;
64+
setChildren: (fieldName: keyof IFormParameters, value: V[]) => void;
65+
} {
5866
const [states, setStates] = useState(initialValues);
5967

6068
const setFieldState = (fieldName: keyof IFormParameters, value: V) => {
@@ -89,7 +97,13 @@ export function useFieldStates<IFormParameters, V>(
8997
});
9098
};
9199

92-
return [states, setStates, setFieldState, setChildState, setChildStates];
100+
return {
101+
all: states,
102+
setAll: setStates,
103+
setFor: setFieldState,
104+
setChild: setChildState,
105+
setChildren: setChildStates,
106+
};
93107
}
94108

95109
export type FieldStatus =

src/lib/generatorFormParameters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export type FormParameters = {
3636
defaultMaxAge?: number;
3737
};
3838

39+
// TODO: make this dependent on initial values which ought to be moved here.
40+
3941
const emptyFormState: FormFieldStates<
4042
FormParameters,
4143
VerboseFieldState | undefined
@@ -65,6 +67,7 @@ export function getEmptyFormState() {
6567
* @param key The FormParameters key as string.
6668
* @returns FormParameters key
6769
*/
70+
// TODO: does this warn if parameters are added?
6871
export function getFormParametersKey(key: string): keyof FormParameters {
6972
const parameterMap: Record<string, keyof FormParameters> = {
7073
clientId: "clientId",

0 commit comments

Comments
 (0)