-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: setup MapAndLabel with React Context and schema input fields pe…
…r map feature (#3501)
- Loading branch information
1 parent
0d83c4d
commit 7bbd1c0
Showing
11 changed files
with
2,146 additions
and
2,139 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 0 additions & 106 deletions
106
editor.planx.uk/src/@planx/components/MapAndLabel/Public.tsx
This file was deleted.
Oops, something went wrong.
155 changes: 155 additions & 0 deletions
155
editor.planx.uk/src/@planx/components/MapAndLabel/Public/Context.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { useSchema } from "@planx/components/shared/Schema/hook"; | ||
import { | ||
Schema, | ||
SchemaUserData, | ||
SchemaUserResponse, | ||
} from "@planx/components/shared/Schema/model"; | ||
import { | ||
getPreviouslySubmittedData, | ||
makeData, | ||
} from "@planx/components/shared/utils"; | ||
import { PublicProps } from "@planx/components/ui"; | ||
import { FormikProps, useFormik } from "formik"; | ||
import React, { | ||
createContext, | ||
PropsWithChildren, | ||
useContext, | ||
useState, | ||
} from "react"; | ||
|
||
import { MapAndLabel } from "../model"; | ||
|
||
interface MapAndLabelContextValue { | ||
schema: Schema; | ||
activeIndex: number; | ||
saveItem: () => Promise<void>; | ||
editItem: (index: number) => void; | ||
cancelEditItem: () => void; | ||
formik: FormikProps<SchemaUserData>; | ||
validateAndSubmitForm: () => void; | ||
mapAndLabelProps: PublicProps<MapAndLabel>; | ||
errors: { | ||
unsavedItem: boolean; | ||
min: boolean; | ||
max: boolean; | ||
}; | ||
} | ||
|
||
type MapAndLabelProviderProps = PropsWithChildren<PublicProps<MapAndLabel>>; | ||
|
||
const MapAndLabelContext = createContext<MapAndLabelContextValue | undefined>( | ||
undefined, | ||
); | ||
|
||
export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = ( | ||
props, | ||
) => { | ||
const { schema, children, handleSubmit } = props; | ||
const { formikConfig, initialValues: _initialValues } = useSchema({ | ||
schema, | ||
previousValues: getPreviouslySubmittedData(props), | ||
}); | ||
|
||
const formik = useFormik<SchemaUserData>({ | ||
...formikConfig, | ||
onSubmit: (values) => { | ||
const defaultPassportData = makeData(props, values.schemaData)?.["data"]; | ||
|
||
handleSubmit?.({ | ||
data: { | ||
...defaultPassportData, | ||
}, | ||
}); | ||
}, | ||
}); | ||
|
||
const [activeIndex, setActiveIndex] = useState<number>( | ||
props.previouslySubmittedData ? -1 : 0, | ||
); | ||
|
||
const [activeItemInitialState, setActiveItemInitialState] = useState< | ||
SchemaUserResponse | undefined | ||
>(undefined); | ||
|
||
const [unsavedItemError, setUnsavedItemError] = useState<boolean>(false); | ||
const [minError, setMinError] = useState<boolean>(false); | ||
const [maxError, setMaxError] = useState<boolean>(false); | ||
|
||
const resetErrors = () => { | ||
setMinError(false); | ||
setMaxError(false); | ||
setUnsavedItemError(false); | ||
}; | ||
|
||
const saveItem = async () => { | ||
resetErrors(); | ||
|
||
const errors = await formik.validateForm(); | ||
const isValid = !errors.schemaData?.length; | ||
if (isValid) { | ||
exitEditMode(); | ||
} | ||
}; | ||
|
||
const validateAndSubmitForm = () => { | ||
// Do not allow submissions with an unsaved item | ||
if (activeIndex !== -1) return setUnsavedItemError(true); | ||
|
||
// Manually validate minimum number of items | ||
if (formik.values.schemaData.length < schema.min) { | ||
return setMinError(true); | ||
} | ||
|
||
formik.handleSubmit(); | ||
}; | ||
|
||
const cancelEditItem = () => { | ||
if (activeItemInitialState) resetItemToPreviousState(); | ||
|
||
setActiveItemInitialState(undefined); | ||
|
||
exitEditMode(); | ||
}; | ||
|
||
const editItem = (index: number) => { | ||
setActiveItemInitialState(formik.values.schemaData[index]); | ||
setActiveIndex(index); | ||
}; | ||
|
||
const exitEditMode = () => setActiveIndex(-1); | ||
|
||
const resetItemToPreviousState = () => | ||
formik.setFieldValue(`schemaData[${activeIndex}]`, activeItemInitialState); | ||
|
||
return ( | ||
<MapAndLabelContext.Provider | ||
value={{ | ||
activeIndex, | ||
saveItem, | ||
schema, | ||
mapAndLabelProps: props, | ||
editItem, | ||
cancelEditItem, | ||
formik, | ||
validateAndSubmitForm, | ||
errors: { | ||
unsavedItem: unsavedItemError, | ||
min: minError, | ||
max: maxError, | ||
}, | ||
}} | ||
> | ||
{children} | ||
</MapAndLabelContext.Provider> | ||
); | ||
}; | ||
|
||
export const useMapAndLabelContext = (): MapAndLabelContextValue => { | ||
const context = useContext(MapAndLabelContext); | ||
if (!context) { | ||
throw new Error( | ||
"useMapAndLabelContext must be used within a MapAndLabelProvider", | ||
); | ||
} | ||
return context; | ||
}; |
Oops, something went wrong.