Skip to content
This repository has been archived by the owner on Jul 19, 2023. It is now read-only.

Commit

Permalink
feat: save generator form state in sessionStore
Browse files Browse the repository at this point in the history
* simplify formik state handling and add custom useFormik hook.
  • Loading branch information
Laurin-W committed Dec 19, 2022
1 parent 5c96738 commit df93db8
Show file tree
Hide file tree
Showing 5 changed files with 553 additions and 459 deletions.
13 changes: 5 additions & 8 deletions src/components/VerboseTextFieldArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
TextFieldProps,
Typography,
} from "@mui/material";
import { VerboseFieldState } from "../lib/formValidationTypes";
import { FormFieldState, VerboseFieldState } from "../lib/formValidationTypes";
import FieldNameLabel from "./FieldNameLabel";
import NecessityLabel, { Necessity } from "./NecessityLabel";
import VerboseHelperText from "./VerboseHelperText";
Expand All @@ -40,9 +40,8 @@ export type VerboseFieldArrayRenderProps = TextFieldProps & {
componentFieldName?: string;
description: string | string[];
values: string[];
state?: VerboseFieldState | undefined;
state?: FormFieldState<VerboseFieldState | undefined>;
necessity?: Necessity;
childStates?: (VerboseFieldState | undefined)[];
allowEmpty?: boolean;
pushItem(obj: unknown): void;
removeItem(index: number): void;
Expand All @@ -64,7 +63,6 @@ export default function VerboseTextFieldArray(
values,
state = undefined,
necessity = undefined,
childStates = undefined,
allowEmpty = false,

pushItem,
Expand Down Expand Up @@ -95,7 +93,7 @@ export default function VerboseTextFieldArray(
{necessity && <NecessityLabel necessity={necessity} />}
</Grid>

<VerboseHelperText state={state} />
<VerboseHelperText state={state?.state} />

{/* Field name label */}
<Grid container item spacing={1}>
Expand Down Expand Up @@ -136,8 +134,8 @@ export default function VerboseTextFieldArray(
name={`${componentName}.${index}`}
label={rowLabel}
state={
Array.isArray(childStates)
? childStates[index]
Array.isArray(state?.childStates)
? state?.childStates[index]
: undefined
}
value={value}
Expand Down Expand Up @@ -182,7 +180,6 @@ export default function VerboseTextFieldArray(
VerboseTextFieldArray.defaultProps = {
state: undefined,
necessity: undefined,
childStates: undefined,
componentFieldName: undefined,
addRowLabel: "Add row",
allowEmpty: false,
Expand Down
97 changes: 97 additions & 0 deletions src/components/useVerboseFormik.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// Copyright 2022 Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import { FormikValues, useFormik } from "formik";
import {
FormFieldStates,
useFieldStates,
VerboseFieldState,
} from "../lib/formValidationTypes";

/**
* Extends the {@link useFormik} function by adding state property supporting
* verbose field state management.
*
* @param initialValues The initial values for the form fields.
* @param initialStates The initial states of the form fields.
*
* @returns formik object
*/
export default function useVerboseFormik<FormParameters extends FormikValues>({
initialValues,
initialStates,
}: {
initialValues: FormParameters;
initialStates: FormFieldStates<FormParameters, VerboseFieldState | undefined>;
}) {
const states = useFieldStates<FormParameters, VerboseFieldState | undefined>(
initialStates
);

const formik = useFormik<FormParameters>({
initialValues,
onSubmit: () => {},
validateOnBlur: false,
validateOnChange: false,
});

const modifiedFormik: Omit<
typeof formik,
"errors" | "setErrors" | "initialErrors" | "setFieldErrors"
> & {
states: typeof states;
} = { ...formik, states };

// Helper to add an array field's child value.
const addChild = async (fieldName: keyof FormParameters, value: unknown) => {
await formik.setValues({
...formik.values,
[fieldName]: [...formik.values[fieldName], value],
});
states.setChildren(fieldName, [
...(states.all[fieldName].childStates || []),
undefined,
]);
};

// Helper to remove an array field's child value.
const removeChild = async (
fieldName: keyof FormParameters,
index: number
) => {
const values = formik.values[fieldName];
if (!Array.isArray(values)) {
throw new Error(
"Cannot remove child form value from an object that's not an array."
);
}
await formik.setValues({
...formik.values,
[fieldName]: values.filter((_: unknown, i: number) => i !== index),
});
states.setChildren(
fieldName,
states.all[fieldName].childStates?.filter((_, i) => i !== index) || []
);
};

return { ...modifiedFormik, addChild, removeChild };
}
38 changes: 26 additions & 12 deletions src/lib/formValidationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@

import { useState } from "react";

export declare type FormFieldState<State> = {
state?: State;
childStates?: State[];
};

/**
* Provides a type to store states of type State of a form
* with field values of type `Value`.
* Provides a type to store states of type `State` of a form
* with the value keys of type `Value`. Additionally, an array
* `childStates` is provided for nested components.
* Inspired by `FormikErrors<Values>` type.
*/
export declare type FormFieldStates<Values, State> = {
[K in keyof Values]: { state?: State; childStates?: State[] };
[K in keyof Values]: FormFieldState<State>;
};

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

/**
* Helper function to manage field values of verbose forms.
* Helper function to manage validation state of forms.
*
* @param initialValues
* @returns [states, setStates, setFieldState, setArrayFieldState]
*/
export function useFieldStates<IFormParameters, V>(
initialValues: FormFieldStates<IFormParameters, V>
): [
FormFieldStates<IFormParameters, V>,
React.Dispatch<React.SetStateAction<FormFieldStates<IFormParameters, V>>>,
(fieldName: keyof IFormParameters, value: V) => void,
(fieldName: keyof IFormParameters, value: V, index: number) => void,
(fieldName: keyof IFormParameters, value: V[]) => void
] {
): {
all: FormFieldStates<IFormParameters, V>;
setAll: React.Dispatch<
React.SetStateAction<FormFieldStates<IFormParameters, V>>
>;
setFor: (fieldName: keyof IFormParameters, value: V) => void;
setChild: (fieldName: keyof IFormParameters, value: V, index: number) => void;
setChildren: (fieldName: keyof IFormParameters, value: V[]) => void;
} {
const [states, setStates] = useState(initialValues);

const setFieldState = (fieldName: keyof IFormParameters, value: V) => {
Expand Down Expand Up @@ -89,7 +97,13 @@ export function useFieldStates<IFormParameters, V>(
});
};

return [states, setStates, setFieldState, setChildState, setChildStates];
return {
all: states,
setAll: setStates,
setFor: setFieldState,
setChild: setChildState,
setChildren: setChildStates,
};
}

export type FieldStatus =
Expand Down
3 changes: 3 additions & 0 deletions src/lib/generatorFormParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type FormParameters = {
defaultMaxAge?: number;
};

// TODO: make this dependent on initial values which ought to be moved here.

const emptyFormState: FormFieldStates<
FormParameters,
VerboseFieldState | undefined
Expand Down Expand Up @@ -65,6 +67,7 @@ export function getEmptyFormState() {
* @param key The FormParameters key as string.
* @returns FormParameters key
*/
// TODO: does this warn if parameters are added?
export function getFormParametersKey(key: string): keyof FormParameters {
const parameterMap: Record<string, keyof FormParameters> = {
clientId: "clientId",
Expand Down
Loading

0 comments on commit df93db8

Please sign in to comment.