diff --git a/packages/esm-patient-registration-app/src/patient-registration/before-save-prompt.tsx b/packages/esm-patient-registration-app/src/patient-registration/before-save-prompt.tsx index 0db6d6896..94574ab33 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/before-save-prompt.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/before-save-prompt.tsx @@ -49,17 +49,17 @@ const BeforeSavePrompt: React.FC = ({ when, redirect }) = } }, []); - useEffect(() => { - if (when && typeof target === 'undefined') { - window.addEventListener('single-spa:before-routing-event', cancelNavigation); - window.addEventListener('beforeunload', cancelUnload); + // useEffect(() => { + // if (when && typeof target === 'undefined') { + // window.addEventListener('single-spa:before-routing-event', cancelNavigation); + // window.addEventListener('beforeunload', cancelUnload); - return () => { - window.removeEventListener('beforeunload', cancelUnload); - window.removeEventListener('single-spa:before-routing-event', cancelNavigation); - }; - } - }, [target, when, cancelUnload, cancelNavigation]); + // return () => { + // window.removeEventListener('beforeunload', cancelUnload); + // window.removeEventListener('single-spa:before-routing-event', cancelNavigation); + // }; + // } + // }, [target, when, cancelUnload, cancelNavigation]); useEffect(() => { if (typeof target === 'string') { diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-field.component.tsx index d0371c9a4..3b57cf7e9 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-field.component.tsx @@ -9,6 +9,8 @@ import { useOrderedAddressHierarchyLevels } from './address-hierarchy.resource'; import AddressHierarchyLevels from './address-hierarchy-levels.component'; import AddressSearchComponent from './address-search.component'; import styles from '../field.scss'; +import type { FormValues } from '../../patient-registration.types'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; function parseString(xmlDockAsString: string) { const parser = new DOMParser(); @@ -47,16 +49,16 @@ export const AddressComponent: React.FC = () => { }, } = config; - const { setFieldValue } = useContext(PatientRegistrationContext); + const { setValue } = usePatientRegistrationContext(); const { orderedFields, isLoadingFieldOrder, errorFetchingFieldOrder } = useOrderedAddressHierarchyLevels(); useEffect(() => { if (addressTemplate?.elementDefaults) { Object.entries(addressTemplate.elementDefaults).forEach(([name, defaultValue]) => { - setFieldValue(`address.${name}`, defaultValue); + setValue(`address.${name}`, defaultValue); }); } - }, [addressTemplate, setFieldValue]); + }, [addressTemplate, setValue]); const orderedAddressFields = useMemo(() => { if (isLoadingFieldOrder || errorFetchingFieldOrder) { @@ -84,7 +86,7 @@ export const AddressComponent: React.FC = () => { {addressLayout.map((attributes, index) => ( { orderedAddressFields.map((attributes, index) => ( = ({ attribute }) => { const { t } = useTranslation(); - const [field, meta, { setValue }] = useField(`address.${attribute.name}`); + const fieldName = `address.${attribute.name}` as keyof FormValues; + const { setValue, watch } = usePatientRegistrationContext(); + const fieldValue = watch(fieldName); const { fetchEntriesForField, searchString, updateChildElements } = useAddressEntryFetchConfig(attribute.name); const { entries } = useAddressEntries(fetchEntriesForField, searchString); const label = t(attribute.label) + (attribute?.required ? '' : ` (${t('optional', 'optional')})`); const handleInputChange = useCallback( (newValue) => { - setValue(newValue); + setValue(fieldName, newValue); }, [setValue], ); const handleSelection = useCallback( (selectedItem) => { - if (meta.value !== selectedItem) { - setValue(selectedItem); + if (fieldValue !== selectedItem) { + setValue(fieldName, selectedItem); updateChildElements(); } }, - [updateChildElements, meta.value, setValue], + [updateChildElements, fieldValue, setValue], ); return ( ); diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-hierarchy.resource.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-hierarchy.resource.tsx index ed9e59ca8..b583209d1 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-hierarchy.resource.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-hierarchy.resource.tsx @@ -1,8 +1,8 @@ import { useCallback, useContext, useEffect, useMemo } from 'react'; -import { useField } from 'formik'; import useSWRImmutable from 'swr/immutable'; import { type FetchResponse, openmrsFetch } from '@openmrs/esm-framework'; import { PatientRegistrationContext } from '../../patient-registration-context'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; interface AddressFields { addressField: string; @@ -57,8 +57,9 @@ export function useAddressEntries(fetchResults, searchString) { */ export function useAddressEntryFetchConfig(addressField: string) { const { orderedFields, isLoadingFieldOrder } = useOrderedAddressHierarchyLevels(); - const { setFieldValue } = useContext(PatientRegistrationContext); - const [, { value: addressValues }] = useField('address'); + const { setValue } = usePatientRegistrationContext(); + const { watch } = usePatientRegistrationContext(); + const addressValues = watch('address'); const index = useMemo( () => (!isLoadingFieldOrder ? orderedFields.findIndex((field) => field === addressField) : -1), @@ -87,9 +88,9 @@ export function useAddressEntryFetchConfig(addressField: string) { return; } orderedFields.slice(index + 1).map((fieldName) => { - setFieldValue(`address.${fieldName}`, ''); + setValue(`address.${fieldName}`, ''); }); - }, [index, isLoadingFieldOrder, orderedFields, setFieldValue]); + }, [index, isLoadingFieldOrder, orderedFields, setValue]); const results = useMemo( () => ({ diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx index d763dfd3d..9be025ddf 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx @@ -1,9 +1,10 @@ -import React, { useState, useRef, useEffect, useMemo } from 'react'; +import React, { useState, useRef, useEffect, useMemo, useContext } from 'react'; import { useAddressHierarchy } from './address-hierarchy.resource'; import { Search } from '@carbon/react'; import { useTranslation } from 'react-i18next'; import { useFormikContext } from 'formik'; import styles from './address-search.scss'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; interface AddressSearchComponentProps { addressLayout: Array; @@ -12,6 +13,7 @@ interface AddressSearchComponentProps { const AddressSearchComponent: React.FC = ({ addressLayout }) => { const { t } = useTranslation(); const separator = ' > '; + const { control, setValue } = usePatientRegistrationContext(); const searchBox = useRef(null); const wrapper = useRef(null); const [searchString, setSearchString] = useState(''); @@ -29,8 +31,6 @@ const AddressSearchComponent: React.FC = ({ address return [...options]; }, [addresses, searchString]); - const { setFieldValue } = useFormikContext(); - const handleInputChange = (e) => { setSearchString(e.target.value); }; @@ -39,7 +39,7 @@ const AddressSearchComponent: React.FC = ({ address if (address) { const values = address.split(separator); addressLayout.map(({ name }, index) => { - setFieldValue(`address.${name}`, values?.[index] ?? ''); + setValue(`address.${name}`, values?.[index] ?? ''); }); setSearchString(''); } diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/address/custom-address-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/address/custom-address-field.component.tsx index c3baa01c8..270de21e2 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/address/custom-address-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/address/custom-address-field.component.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { Input } from '../../input/basic-input/input/input.component'; import styles from '../field.scss'; import { type FieldDefinition } from '../../../config-schema'; +import type { FormValues } from '../../patient-registration.types'; export interface AddressFieldProps { fieldDefinition: FieldDefinition; @@ -15,18 +16,14 @@ export const AddressField: React.FC = ({ fieldDefinition }) = return (
- - {({ field, form: { touched, errors }, meta }) => { - return ( - - ); - }} - + return ( + + );
); }; diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx index e764d0e81..269e96245 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx @@ -1,17 +1,20 @@ -import React, { useMemo } from 'react'; +import React, { useContext, useMemo } from 'react'; import classNames from 'classnames'; -import { Field, useField } from 'formik'; import { useTranslation } from 'react-i18next'; import { InlineNotification, Layer, Select, SelectItem, SelectSkeleton, TextInput } from '@carbon/react'; import { useConfig } from '@openmrs/esm-framework'; import { type RegistrationConfig } from '../../../config-schema'; import { useConceptAnswers } from '../field.resource'; import styles from '../field.scss'; +import { PatientRegistrationContext } from '../../patient-registration-context'; +import { Controller } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export const CauseOfDeathField: React.FC = () => { const { t } = useTranslation(); const { fieldConfigurations, freeTextFieldConceptUuid } = useConfig(); - const [deathCause, deathCauseMeta] = useField('deathCause'); + const { getFieldState, control } = usePatientRegistrationContext(); + const { error, isTouched } = getFieldState('deathCause'); const conceptUuid = fieldConfigurations?.causeOfDeath?.conceptUuid; const required = fieldConfigurations?.causeOfDeath?.required; @@ -50,15 +53,17 @@ export const CauseOfDeathField: React.FC = () => { /> ) : ( <> - - {({ field, form: { touched, errors }, meta }) => { - return ( + ( + <> - ); - }} - - {deathCause.value === freeTextFieldConceptUuid && ( -
- - {({ field, form: { touched, errors }, meta }) => { - return ( - - - - ); - }} - -
- )} + {field.value === freeTextFieldConceptUuid && ( +
+ { + return ( + + + + ); + }} + /> +
+ )} + + )} + /> )} diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx index 0a27740f4..b31555717 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx @@ -3,11 +3,11 @@ import classNames from 'classnames'; import dayjs from 'dayjs'; import { Layer, SelectItem, TimePicker, TimePickerSelect } from '@carbon/react'; import { useTranslation } from 'react-i18next'; -import { useField } from 'formik'; import { OpenmrsDatePicker } from '@openmrs/esm-framework'; import { PatientRegistrationContext } from '../../patient-registration-context'; -import type { FormValues } from '../../patient-registration.types'; import styles from '../field.scss'; +import { Controller } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export const DateAndTimeOfDeathField: React.FC = () => { const { t } = useTranslation(); @@ -24,61 +24,75 @@ export const DateAndTimeOfDeathField: React.FC = () => { }; function DeathDateField() { - const { values, setFieldValue } = useContext(PatientRegistrationContext); - const [deathDate, deathDateMeta] = useField('deathDate'); + const { watch, setValue, control } = usePatientRegistrationContext(); + const isDead = watch('isDead'); const { t } = useTranslation(); const today = dayjs().hour(23).minute(59).second(59).toDate(); - const onDateChange = useCallback( - (selectedDate: Date) => { - setFieldValue( - 'deathDate', - selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined, - ); - }, - [deathDate], - ); + const onDateChange = useCallback((selectedDate: Date) => { + setValue( + 'deathDate', + selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined, + ); + }, []); return ( - - - + ( + + + + )} + /> ); } function DeathTimeField() { const { t } = useTranslation(); - const [deathTimeField, deathTimeMeta] = useField('deathTime'); - const [deathTimeFormatField, deathTimeFormatMeta] = useField('deathTimeFormat'); + const { control } = usePatientRegistrationContext(); return ( - - - - - - - - + ( + + + ( + + + + + )} + /> + + + )} + /> ); } diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx index 6599103ec..2f1ed260d 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx @@ -1,11 +1,12 @@ -import React, { type ChangeEvent, useCallback, useContext } from 'react'; -import { ContentSwitcher, Layer, Switch, TextInput } from '@carbon/react'; +import React, { useCallback } from 'react'; +import { ContentSwitcher, Layer, Switch } from '@carbon/react'; import { useTranslation } from 'react-i18next'; -import { useField } from 'formik'; -import { PatientRegistrationContext } from '../../patient-registration-context'; import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework'; import { type RegistrationConfig } from '../../../config-schema'; import styles from '../field.scss'; +import { Controller, useFormContext } from 'react-hook-form'; +import useZodSchema from '../../useZodSchema'; +import { NumberInput } from '@carbon/react'; const calcBirthdate = (yearDelta, monthDelta, dateOfBirth) => { const { enabled, month, dayOfMonth } = dateOfBirth.useEstimatedDateOfBirth; @@ -26,66 +27,67 @@ export const DobField: React.FC = () => { fieldConfigurations: { dateOfBirth }, } = useConfig(); const allowEstimatedBirthDate = dateOfBirth?.allowEstimatedDateOfBirth; - const [{ value: dobUnknown }] = useField('birthdateEstimated'); - const [birthdate, birthdateMeta] = useField('birthdate'); - const [yearsEstimated, yearsEstimateMeta] = useField('yearsEstimated'); - const [monthsEstimated, monthsEstimateMeta] = useField('monthsEstimated'); - const { setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext); + const { watch, control, setValue } = useFormContext(); + const dobUnknown = watch('birthdateEstimated'); const today = new Date(); + const { updateZodSchema } = useZodSchema(); + + // useEffect(() => { + // const yearsEstimatedSchema = z + // .number({ + // invalid_type_error: t('yearsEstimatedTypeError', 'Years estimated must be a number'), + // }) + // .min(0) + // .max(140, { + // message: t('maxError', 'Max error'), + // }); + // updateZodSchema('yearsEstimated', dobUnknown ? yearsEstimatedSchema : yearsEstimatedSchema.optional()); + // }, [t, dobUnknown]); + const onToggle = useCallback( (e: { name?: string | number }) => { - setFieldValue('birthdateEstimated', e.name === 'unknown'); - setFieldValue('birthdate', ''); - setFieldValue('yearsEstimated', 0); - setFieldValue('monthsEstimated', ''); - setFieldTouched('birthdateEstimated', true, false); - }, - [setFieldValue], - ); - - const onDateChange = useCallback( - (birthdate: Date) => { - setFieldValue('birthdate', birthdate); - setFieldTouched('birthdate', true, false); + setValue('birthdateEstimated', e.name === 'unknown'); + setValue('birthdate', ''); + setValue('yearsEstimated', undefined); + setValue('monthsEstimated', undefined); + // setFieldTouched('birthdateEstimated', true, false); }, - [setFieldValue, setFieldTouched], + [setValue], ); - const onEstimatedYearsChange = useCallback( - (ev: ChangeEvent) => { - const years = +ev.target.value; + // TODO: Make this validated as different fields + // const onEstimatedYearsChange = useCallback( + // (ev: ChangeEvent) => { + // const years = +ev.target.value; - if (!isNaN(years) && years < 140 && years >= 0) { - setFieldValue('yearsEstimated', years); - setFieldValue('birthdate', calcBirthdate(years, monthsEstimateMeta.value, dateOfBirth)); - } - }, - [setFieldValue, dateOfBirth, monthsEstimateMeta.value], - ); + // if (!isNaN(years) && years < 140 && years >= 0) { + // setValue('yearsEstimated', years); + // setValue('birthdate', calcBirthdate(years, monthsEstimated, dateOfBirth)); + // } + // }, + // [setValue, dateOfBirth, monthsEstimated], + // ); - const onEstimatedMonthsChange = useCallback( - (ev: ChangeEvent) => { - const months = +ev.target.value; + // const onEstimatedMonthsChange = useCallback( + // (ev: ChangeEvent) => { + // const months = +ev.target.value; - if (!isNaN(months)) { - setFieldValue('monthsEstimated', months); - setFieldValue('birthdate', calcBirthdate(yearsEstimateMeta.value, months, dateOfBirth)); - } - }, - [setFieldValue, dateOfBirth, yearsEstimateMeta.value], - ); + // if (!isNaN(months)) { + // setValue('monthsEstimated', months); + // setValue('birthdate', calcBirthdate(yearsEstimated, months, dateOfBirth)); + // } + // }, + // [setValue, dateOfBirth, yearsEstimated], + // ); - const updateBirthdate = useCallback(() => { - const months = +monthsEstimateMeta.value % 12; - const years = +yearsEstimateMeta.value + Math.floor(monthsEstimateMeta.value / 12); - setFieldValue('yearsEstimated', years); - setFieldValue('monthsEstimated', months > 0 ? months : ''); - setFieldValue('birthdate', calcBirthdate(years, months, dateOfBirth)); - setFieldTouched('yearsEstimated', true, false); - setFieldTouched('monthsEstimated', true, false); - setFieldTouched('birthdate', true, false); - }, [setFieldValue, setFieldTouched, monthsEstimateMeta, yearsEstimateMeta, dateOfBirth]); + // const updateBirthdate = useCallback(() => { + // const months = +monthsEstimated % 12; + // const years = +yearsEstimated + Math.floor(monthsEstimated / 12); + // setValue('yearsEstimated', years); + // setValue('monthsEstimated', months > 0 ? months : undefined); + // setValue('birthdate', calcBirthdate(years, months, dateOfBirth)); + // }, [setValue, monthsEstimated, yearsEstimated, dateOfBirth]); return (
@@ -104,58 +106,58 @@ export const DobField: React.FC = () => { {!dobUnknown ? (
- setFieldTouched('birthdate', true, false)} - maxDate={today} - labelText={t('dateOfBirthLabelText', 'Date of birth')} - isInvalid={!!(birthdateMeta.touched && birthdateMeta.error)} - invalidText={t(birthdateMeta.error)} - value={birthdate.value} + ( + + )} />
) : (
- { - yearsEstimated.onBlur(e); - setFieldTouched('yearsEstimated', true, false); - updateBirthdate(); + { + return ( + + ); }} />
- { - monthsEstimated.onBlur(e); - setFieldTouched('monthsEstimated', true, false); - updateBirthdate(); - }} + ( + + )} />
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx index 75fa4663a..4a9d0d592 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx @@ -2,22 +2,18 @@ import React, { useContext } from 'react'; import { RadioButton, RadioButtonGroup } from '@carbon/react'; import { useTranslation } from 'react-i18next'; import { PatientRegistrationContext } from '../../patient-registration-context'; -import { useField } from 'formik'; import { type RegistrationConfig } from '../../../config-schema'; import { useConfig } from '@openmrs/esm-framework'; import styles from '../field.scss'; +import { Controller } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export const GenderField: React.FC = () => { const { fieldConfigurations } = useConfig(); const { t } = useTranslation(); - const [field, meta] = useField('gender'); - const { setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext); + const { control } = usePatientRegistrationContext(); const fieldConfigs = fieldConfigurations?.gender; - const setGender = (gender: string) => { - setFieldValue('gender', gender); - setFieldTouched('gender', true, false); - }; /** * DO NOT REMOVE THIS COMMENT HERE, ADDS TRANSLATION FOR SEX OPTIONS * t('male', 'Male') @@ -31,19 +27,27 @@ export const GenderField: React.FC = () => {

{t('sexFieldLabelText', 'Sex')}

{t('genderLabelText', 'Sex')}

- - {fieldConfigs.map((option) => ( - - ))} - - {meta.touched && meta.error && ( -
{t(meta.error, 'Gender is required')}
- )} + ( + + {fieldConfigs.map((option) => ( + + ))} + + )} + />
); diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx index cfd27ba5e..72a5091ae 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx @@ -14,6 +14,7 @@ import type { } from '../../patient-registration.types'; import { ResourcesContext } from '../../../offline.resources'; import styles from '../field.scss'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export function setIdentifierSource( identifierSource: IdentifierSource, @@ -59,7 +60,13 @@ export function deleteIdentifierType(identifiers: FormValues['identifiers'], ide export const Identifiers: React.FC = () => { const { identifierTypes } = useContext(ResourcesContext); const isLoading = !identifierTypes?.length; - const { values, setFieldValue, initialFormValues, isOffline } = useContext(PatientRegistrationContext); + const { + watch, + formState: { defaultValues }, + setValue, + isOffline, + } = usePatientRegistrationContext(); + const identifiers = watch('identifiers'); const { t } = useTranslation(); const layout = useLayoutType(); const [showIdentifierOverlay, setShowIdentifierOverlay] = useState(false); @@ -78,11 +85,11 @@ export const Identifiers: React.FC = () => { (defaultIdentifierTypeUuid) => defaultIdentifierTypeUuid === type.uuid, ), ) - .filter((type) => !values.identifiers[type.fieldName]) + .filter((type) => !identifiers[type.fieldName]) .forEach((type) => { identifiers[type.fieldName] = initializeIdentifier( type, - values.identifiers[type.uuid] ?? initialFormValues.identifiers[type.uuid] ?? {}, + identifiers[type.uuid] ?? defaultValues.identifiers[type.uuid] ?? {}, ); }); /* @@ -91,14 +98,14 @@ export const Identifiers: React.FC = () => { fall into an infinite run. */ if (Object.keys(identifiers).length) { - setFieldValue('identifiers', { - ...values.identifiers, + setValue('identifiers', { + ...identifiers, ...identifiers, }); } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [identifierTypes, setFieldValue, defaultPatientIdentifierTypes, values.identifiers, initializeIdentifier]); + }, [identifierTypes, setValue, defaultPatientIdentifierTypes, identifiers, initializeIdentifier]); const closeIdentifierSelectionOverlay = useCallback( () => setShowIdentifierOverlay(false), @@ -131,11 +138,12 @@ export const Identifiers: React.FC = () => {
- {Object.entries(values.identifiers).map(([fieldName, identifier]) => ( + {Object.entries(identifiers).map(([fieldName, identifier]) => ( + // @ts-ignore ))} {showIdentifierOverlay && ( - + )}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/id/identifier-selection-overlay.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/id/identifier-selection-overlay.component.tsx index b181c5114..97d727003 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/id/identifier-selection-overlay.component.tsx @@ -12,17 +12,25 @@ import { } from '../../input/custom-input/identifier/utils'; import { initializeIdentifier, setIdentifierSource } from './id-field.component'; import styles from './identifier-selection.scss'; +import { type UseFormSetValue } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; interface PatientIdentifierOverlayProps { - setFieldValue: (string, PatientIdentifierValue) => void; + setFieldValue: UseFormSetValue; closeOverlay: () => void; } const PatientIdentifierOverlay: React.FC = ({ closeOverlay, setFieldValue }) => { const layout = useLayoutType(); const { identifierTypes } = useContext(ResourcesContext); - const { isOffline, values, initialFormValues } = useContext(PatientRegistrationContext); - const [unsavedIdentifierTypes, setUnsavedIdentifierTypes] = useState(values.identifiers); + const { + isOffline, + watch, + formState: { defaultValues }, + } = usePatientRegistrationContext(); + const identifiers = watch('identifiers'); + const [unsavedIdentifierTypes, setUnsavedIdentifierTypes] = useState(identifiers); + console.log({ unsavedIdentifierTypes }); const [searchString, setSearchString] = useState(''); const { t } = useTranslation(); const { defaultPatientIdentifierTypes } = useConfig(); @@ -35,8 +43,8 @@ const PatientIdentifierOverlay: React.FC = ({ clo }, [defaultPatientIdentifierTypes]); useEffect(() => { - setUnsavedIdentifierTypes(values.identifiers); - }, [values.identifiers]); + setUnsavedIdentifierTypes(identifiers); + }, [identifiers]); const handleSearch = useCallback((event) => setSearchString(event?.target?.value ?? ''), []); @@ -53,9 +61,7 @@ const PatientIdentifierOverlay: React.FC = ({ clo ...unsavedIdentifierTypes, [identifierType.fieldName]: initializeIdentifier( identifierType, - values.identifiers[identifierType.fieldName] ?? - initialFormValues.identifiers[identifierType.fieldName] ?? - {}, + identifiers[identifierType.fieldName] ?? defaultValues.identifiers[identifierType.fieldName] ?? {}, ), }; } @@ -66,7 +72,7 @@ const PatientIdentifierOverlay: React.FC = ({ clo } return unsavedIdentifierTypes; }), - [initialFormValues.identifiers, values.identifiers], + [defaultValues.identifiers, identifiers], ); const handleSelectingIdentifierSource = (identifierType: PatientIdentifierType, sourceUuid) => @@ -92,7 +98,7 @@ const PatientIdentifierOverlay: React.FC = ({ clo defaultPatientIdentifierTypesMap[identifierType.uuid] || // De-selecting shouldn't be allowed if the identifier was selected earlier and is present in the form. // If the user wants to de-select an identifier-type already present in the form, they'll need to delete the particular identifier from the form itself. - values.identifiers[identifierType.fieldName]; + identifiers[identifierType.fieldName]; const isDisabledOffline = isOffline && shouldBlockPatientIdentifierInOfflineMode(identifierType); return ( @@ -149,7 +155,7 @@ const PatientIdentifierOverlay: React.FC = ({ clo filteredIdentifiers, unsavedIdentifierTypes, defaultPatientIdentifierTypesMap, - values.identifiers, + identifiers, isOffline, handleCheckingIdentifier, t, @@ -158,7 +164,7 @@ const PatientIdentifierOverlay: React.FC = ({ clo const handleConfiguringIdentifiers = useCallback(() => { setFieldValue('identifiers', unsavedIdentifierTypes); - closeOverlay(); + // closeOverlay(); }, [unsavedIdentifierTypes, setFieldValue, closeOverlay]); return ( diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx index f2b4e2147..29c1c913b 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx @@ -1,12 +1,12 @@ -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { ContentSwitcher, Switch } from '@carbon/react'; -import { useField } from 'formik'; import { ExtensionSlot, useConfig } from '@openmrs/esm-framework'; import { Input } from '../../input/basic-input/input/input.component'; -import { PatientRegistrationContext } from '../../patient-registration-context'; import { type RegistrationConfig } from '../../../config-schema'; import styles from '../field.scss'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; +import type { FormValues } from '../../patient-registration.types'; export const unidentifiedPatientAttributeTypeUuid = '8b56eac7-5c76-4b9c-8c6f-1deab8d3fc47'; const containsNoNumbers = /^([^0-9]*)$/; @@ -21,7 +21,7 @@ function checkNumber(value: string) { export const NameField = () => { const { t } = useTranslation(); - const { setCapturePhotoProps, currentPhoto, setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext); + const { setCapturePhotoProps, currentPhoto, setValue, control, watch } = usePatientRegistrationContext(); const { fieldConfigurations: { name: { @@ -35,8 +35,14 @@ export const NameField = () => { }, } = useConfig(); - const [{ value: isPatientUnknownValue }, , { setValue: setUnknownPatient }] = useField( - `attributes.${unidentifiedPatientAttributeTypeUuid}`, + const unknownPatientFieldName = useMemo( + () => `attributes.${unidentifiedPatientAttributeTypeUuid}` as keyof FormValues, + [], + ); + const isPatientUnknownValue = watch(unknownPatientFieldName); + const setUnknownPatient = useCallback( + (value: string) => setValue(unknownPatientFieldName, value), + [unknownPatientFieldName, setValue], ); const isPatientUnknown = isPatientUnknownValue === 'true'; @@ -48,7 +54,7 @@ export const NameField = () => { imageData: dataUri, dateTime: photoDateTime, }); - setFieldTouched('photo', true, false); + // setFieldTouched('photo', true, false); } }, [setCapturePhotoProps], @@ -56,17 +62,17 @@ export const NameField = () => { const toggleNameKnown = (e) => { if (e.name === 'known') { - setFieldValue('givenName', ''); - setFieldValue('familyName', ''); + setValue('givenName', ''); + setValue('familyName', ''); setUnknownPatient('false'); } else { - setFieldValue('givenName', defaultUnknownGivenName); - setFieldValue('familyName', defaultUnknownFamilyName); + setValue('givenName', defaultUnknownGivenName); + setValue('familyName', defaultUnknownFamilyName); setUnknownPatient('true'); } - setFieldTouched('givenName', true); - setFieldTouched('familyName', true); - setFieldTouched(`attributes.${unidentifiedPatientAttributeTypeUuid}`, true, false); + // setFieldTouched('givenName', true); + // setFieldTouched('familyName', true); + // setFieldTouched(`attributes.${unidentifiedPatientAttributeTypeUuid}`, true, false); }; const firstNameField = ( diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx index 7eecbf520..6cf118578 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx @@ -4,12 +4,14 @@ import { Field } from 'formik'; import { useTranslation } from 'react-i18next'; import { InlineNotification, Layer, Select, SelectItem } from '@carbon/react'; import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework'; -import { type ConceptResponse } from '../../patient-registration.types'; +import type { FormValues, ConceptResponse } from '../../patient-registration.types'; import { type FieldDefinition, type RegistrationConfig } from '../../../config-schema'; import { Input } from '../../input/basic-input/input/input.component'; import { useConcept, useConceptAnswers } from '../field.resource'; import { PatientRegistrationContext } from '../../patient-registration-context'; import styles from './../field.scss'; +import { Controller } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export interface ObsFieldProps { fieldDefinition: FieldDefinition; @@ -94,6 +96,7 @@ interface TextObsFieldProps { function TextObsField({ concept, validationRegex, label, required }: TextObsFieldProps) { const { t } = useTranslation(); + // TODO: Add validation in the ZOD Schema const validateInput = (value: string) => { if (!value || !validationRegex || validationRegex === '' || typeof validationRegex !== 'string' || value === '') { return; @@ -109,19 +112,13 @@ function TextObsField({ concept, validationRegex, label, required }: TextObsFiel const fieldName = `obs.${concept.uuid}`; return (
- - {({ field, form: { touched, errors }, meta }) => { - return ( - - ); - }} - +
); } @@ -137,20 +134,14 @@ function NumericObsField({ concept, label, required }: NumericObsFieldProps) { return (
- - {({ field, form: { touched, errors }, meta }) => { - return ( - - ); - }} - +
); } @@ -166,33 +157,34 @@ interface DateObsFieldProps { function DateObsField({ concept, label, required, placeholder }: DateObsFieldProps) { const { t } = useTranslation(); const fieldName = `obs.${concept.uuid}`; - const { setFieldValue } = useContext(PatientRegistrationContext); + const { setValue } = usePatientRegistrationContext(); + const { control } = usePatientRegistrationContext(); const onDateChange = (date: Date) => { - setFieldValue(fieldName, date); + setValue(fieldName as keyof FormValues, date); }; return (
- - {({ field, form: { touched, errors }, meta }) => { - return ( - <> - - - ); - }} - + ( + <> + + + )} + />
); @@ -209,6 +201,7 @@ interface CodedObsFieldProps { function CodedObsField({ concept, answerConceptSetUuid, label, required, customConceptAnswers }: CodedObsFieldProps) { const { t } = useTranslation(); const fieldName = `obs.${concept.uuid}`; + const { control } = usePatientRegistrationContext(); const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers( customConceptAnswers.length ? '' : answerConceptSetUuid ?? concept.uuid, @@ -227,30 +220,30 @@ function CodedObsField({ concept, answerConceptSetUuid, label, required, customC return (
{!isLoadingConceptAnswers ? ( - - {({ field, form: { touched, errors }, meta }) => { - return ( - - - - ); - }} - + ( + + + + )} + /> ) : null}
); diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx index e5b06a050..f8f5ac71b 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx @@ -3,10 +3,12 @@ import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { Field } from 'formik'; import { Layer, Select, SelectItem } from '@carbon/react'; -import { type PersonAttributeTypeResponse } from '../../patient-registration.types'; +import type { FormValues, PersonAttributeTypeResponse } from '../../patient-registration.types'; import { useConceptAnswers } from '../field.resource'; import styles from './../field.scss'; -import { reportError } from '@openmrs/esm-framework'; +import { fetchCurrentPatient, reportError } from '@openmrs/esm-framework'; +import { Controller } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export interface CodedPersonAttributeFieldProps { id: string; @@ -28,6 +30,7 @@ export function CodedPersonAttributeField({ const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers( customConceptAnswers.length ? '' : answerConceptSetUuid, ); + const { control } = usePatientRegistrationContext(); const { t } = useTranslation(); const fieldName = `attributes.${personAttributeType.uuid}`; @@ -93,26 +96,25 @@ export function CodedPersonAttributeField({
{!isLoadingConceptAnswers ? ( - - {({ field, form: { touched, errors }, meta }) => { - return ( - <> - - - ); - }} - + ( + + )} + /> ) : null}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx index 16b9c6f86..32c528b8f 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx @@ -3,8 +3,10 @@ import classNames from 'classnames'; import { Field } from 'formik'; import { useTranslation } from 'react-i18next'; import { Input } from '../../input/basic-input/input/input.component'; -import { type PersonAttributeTypeResponse } from '../../patient-registration.types'; +import type { FormValues, PersonAttributeTypeResponse } from '../../patient-registration.types'; import styles from './../field.scss'; +import { Controller } from 'react-hook-form'; +import { usePatientRegistrationContext } from '../../patient-registration-hooks'; export interface TextPersonAttributeFieldProps { id: string; @@ -39,20 +41,12 @@ export function TextPersonAttributeField({ return (
- - {({ field, form: { touched, errors }, meta }) => { - return ( - - ); - }} - +
); } diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx index fa6edca8a..43c57e06b 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx @@ -1,7 +1,10 @@ -import React, { useMemo } from 'react'; +import React, { useContext, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Layer, TextInput } from '@carbon/react'; -import { useField } from 'formik'; +import { Controller, type ControllerRenderProps } from 'react-hook-form'; +import { type FormValues } from '../../../patient-registration.types'; +import { PatientRegistrationContext } from '../../../patient-registration-context'; +import { usePatientRegistrationContext } from '../../../patient-registration-hooks'; // FIXME Temporarily imported here export interface TextInputProps @@ -124,12 +127,14 @@ export interface TextInputProps } interface InputProps extends TextInputProps { - checkWarning?(value: string): string; + checkWarning?(value: string | number): string; + name: keyof FormValues; } export const Input: React.FC = ({ checkWarning, ...props }) => { - const [field, meta] = useField(props.name); const { t } = useTranslation(); + const { getFieldState, control, watch } = usePatientRegistrationContext(); + const { error } = getFieldState(props.name); /* Do not remove these comments @@ -151,8 +156,8 @@ export const Input: React.FC = ({ checkWarning, ...props }) => { t('nonCodedCauseOfDeathRequired', 'Non-coded cause of death is required') */ - const value = field.value || ''; - const invalidText = meta.error && t(meta.error); + const value = watch(props.name) as string; + const invalidText = error?.message; const warnText = useMemo(() => { if (!invalidText && typeof checkWarning === 'function') { const warning = checkWarning(value); @@ -166,18 +171,23 @@ export const Input: React.FC = ({ checkWarning, ...props }) => { return (
- - - + ( + + + + )} + />
); }; diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.component.tsx index c120bbc83..165c8007b 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.component.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Layer, Select, SelectItem } from '@carbon/react'; -import { useField } from 'formik'; import { useTranslation } from 'react-i18next'; interface SelectInputProps { @@ -11,7 +10,6 @@ interface SelectInputProps { } export const SelectInput: React.FC = ({ name, options, label, required }) => { - const [field] = useField(name); const { t } = useTranslation(); const selectOptions = [