Skip to content

Commit 8b9088b

Browse files
svjsonmomentiris
andauthored
Fix/uth 242 housing ref form sync fix (#98)
* Fixes React.Strict-incompatible form state synchronization * Auto-focus the search field on load, for fun and glory. (But above all SANITY, when testing and reload the page a lot.) * Simple matrix-based rules for snatching conditional fields from form model, and reduced the amount of form tsx code as a result * Removed non-editable fields from form model and submit payload * Allow empty number fields while typing + validate on submit --------- Co-authored-by: Andreas Lundqvist <momentiris@gmail.com> Co-authored-by: Sven Johansson <sven.johansson@iteam.se>
1 parent 4725d5f commit 8b9088b

22 files changed

+390
-508
lines changed

packages/frontend/src/pages/ParkingSpaces/components/create-applicant-for-listing/SearchContact.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ type SearchContactProps = {
1616
placeholder?: string
1717
onSelect: (contact: ContactSearchData | null) => void
1818
contact: ContactSearchData | null
19+
inputRef: any
1920
}
2021

2122
export const SearchContact = ({
2223
onSelect,
2324
contact,
2425
placeholder = 'Sök boende',
26+
inputRef,
2527
}: SearchContactProps) => {
2628
const [searchString, setSearchString] = useState<string>('')
2729
const contactsQuery = useSearchContacts(searchString)
@@ -54,6 +56,7 @@ export const SearchContact = ({
5456
renderInput={(params) => (
5557
<TextField
5658
{...params}
59+
inputRef={inputRef}
5760
size="small"
5861
variant="outlined"
5962
placeholder={placeholder}

packages/frontend/src/pages/Residences/Residences.tsx

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react'
1+
import React, { useEffect, useRef, useState } from 'react'
22
import {
33
Button,
44
Container,
@@ -10,22 +10,27 @@ import {
1010
} from '@mui/material'
1111
import { useForm, SubmitHandler, FormProvider } from 'react-hook-form'
1212
import { toast } from 'react-toastify'
13-
import dayjs from 'dayjs'
1413
import { Contact, schemas } from 'onecore-types'
14+
import dayjs from 'dayjs'
1515
import { z } from 'zod'
1616

1717
import { SearchContact } from '../ParkingSpaces/components/create-applicant-for-listing/SearchContact'
1818
import { ContactSearchData } from '../ParkingSpaces/components/create-applicant-for-listing/types'
1919
import CustomerInformation from './components/CustomerInformation'
2020
import HousingType from './components/Form/HousingType'
21-
import HousingReferenceReviewStatus from './components/Form/ReviewStatus'
21+
import ReviewStatusSection from './components/ReviewStatusSection'
2222
import HousingReferenceComment from './components/Form/HousingReferenceComment'
2323
import CustomerReference from './components/CustomerReference'
2424
import { useCustomerCard } from './hooks/useCustomerCard'
2525
import {
2626
useCreateOrUpdateApplicationProfile,
2727
UpdateApplicationProfileRequestParamsSchema,
2828
} from './hooks/useCreateOrUpdateApplicationProfile'
29+
import {
30+
housingFieldMatrix,
31+
reviewStatusFieldMatrix,
32+
} from './model/conditional'
33+
import { setConditionalFields } from '../../utils/transform-model'
2934

3035
type HousingTypes = z.infer<
3136
typeof schemas.v1.ApplicationProfileHousingTypeSchema
@@ -46,10 +51,6 @@ export type Inputs = {
4651
comment: string
4752
email: string
4853
expiresAt: dayjs.Dayjs
49-
reviewedBy: string | null
50-
reviewedAt: dayjs.Dayjs
51-
lastAdminUpdatedAt: dayjs.Dayjs
52-
lastApplicantUpdatedAt: dayjs.Dayjs
5354
phone: string
5455
reasonRejected: RejectedReasons | ''
5556
reviewStatus: ReviewStatus
@@ -62,28 +63,33 @@ export type Inputs = {
6263
const getContactsMainPhoneNumber = (contact: Contact) =>
6364
contact.phoneNumbers?.find(({ isMainNumber }) => isMainNumber)?.phoneNumber
6465

65-
const emptyStringToNull = (val: string | null | undefined): string | null =>
66-
val == null || val.trim() === '' ? null : val
66+
const formDefaults = (): Inputs => {
67+
return {
68+
housingType: '',
69+
housingTypeDescription: '',
70+
housingReference: {
71+
comment: '',
72+
email: '',
73+
expiresAt: dayjs(),
74+
phone: '',
75+
reasonRejected: '',
76+
reviewStatus: 'PENDING',
77+
},
78+
landlord: '',
79+
numAdults: 1,
80+
numChildren: 0,
81+
}
82+
}
6783

6884
const ResidencesPage: React.FC = () => {
69-
const { handleSubmit, ...formMethods } = useForm<Inputs>({
85+
const formMethods = useForm<Inputs>({
7086
defaultValues: {
71-
housingType: '',
72-
housingTypeDescription: '',
73-
housingReference: {
74-
comment: '',
75-
email: '',
76-
expiresAt: dayjs(),
77-
phone: '',
78-
reasonRejected: '',
79-
reviewStatus: 'PENDING',
80-
},
81-
landlord: '',
82-
numAdults: 1,
83-
numChildren: 0,
87+
...formDefaults(),
8488
},
8589
})
8690

91+
const { handleSubmit, reset } = formMethods
92+
8793
const [selectedContact, setSelectedContact] =
8894
useState<ContactSearchData | null>(null)
8995

@@ -98,17 +104,28 @@ const ResidencesPage: React.FC = () => {
98104
const createOrUpdateApplicationProfile = useCreateOrUpdateApplicationProfile()
99105

100106
const onSubmit: SubmitHandler<Inputs> = (data: Inputs) => {
101-
const parsed = UpdateApplicationProfileRequestParamsSchema.safeParse({
102-
...data,
103-
landlord: emptyStringToNull(data.landlord),
107+
const payload = {
108+
...formDefaults(),
109+
housingType: data.housingType,
110+
numAdults: data.numAdults,
111+
numChildren: data.numChildren,
104112
housingReference: {
105-
...data.housingReference,
106-
reasonRejected: emptyStringToNull(data.housingReference.reasonRejected),
107-
email: emptyStringToNull(data.housingReference.email),
108-
phone: emptyStringToNull(data.housingReference.phone),
109-
expiresAt: null,
113+
...formDefaults().housingReference,
114+
reviewStatus: data.housingReference.reviewStatus,
115+
reasonRejected: null,
110116
},
111-
})
117+
}
118+
119+
setConditionalFields(data, housingFieldMatrix, data.housingType, payload)
120+
setConditionalFields(
121+
data,
122+
reviewStatusFieldMatrix,
123+
data.housingReference.reviewStatus,
124+
payload
125+
)
126+
127+
const parsed =
128+
UpdateApplicationProfileRequestParamsSchema.safeParse(payload)
112129

113130
if (parsed.success) {
114131
createOrUpdateApplicationProfile.mutate(
@@ -157,15 +174,13 @@ const ResidencesPage: React.FC = () => {
157174
comment: '',
158175
email: '',
159176
expiresAt: dayjs(),
160-
reviewedAt: dayjs(),
161-
reviewedBy: '',
162177
phone: '',
163178
reasonRejected: '',
164179
reviewStatus: 'PENDING',
165180
},
166181
} = customerCard.applicationProfile ?? {}
167182

168-
formMethods.reset({
183+
reset({
169184
housingType: housingType || '',
170185
housingTypeDescription: housingTypeDescription || '',
171186
landlord: landlord || '',
@@ -175,34 +190,35 @@ const ResidencesPage: React.FC = () => {
175190
comment: housingReference.comment || '',
176191
email: housingReference.email || '',
177192
expiresAt: dayjs(housingReference.expiresAt),
178-
reviewedAt: dayjs(housingReference.reviewedAt),
179-
reviewedBy: housingReference.reviewedBy,
180193
phone: housingReference.phone || '',
181194
reasonRejected: housingReference.reasonRejected || '',
182195
reviewStatus: housingReference.reviewStatus || '',
183196
},
184197
})
185198
}
186199
if (isError) {
187-
formMethods.reset()
200+
reset()
188201
}
189-
// Using exhaustive-deps rule leads to an infinite loop, using `status`
190-
// instead
191-
// eslint-disable-next-line react-hooks/exhaustive-deps
192-
}, [status])
202+
}, [customerCard?.applicationProfile, isError, isSuccess, reset, status])
193203

194204
const housingReference = customerCard?.applicationProfile?.housingReference
195205

206+
const inputRef = useRef<HTMLInputElement | null>(null)
207+
208+
useEffect(() => {
209+
inputRef.current?.focus()
210+
}, [])
211+
196212
return (
197213
<Stack spacing={4} padding={0}>
198214
<Typography variant="h1">Sökandeprofil</Typography>
199-
200215
<Container maxWidth="md" disableGutters>
201216
<Stack spacing={2}>
202217
<SearchContact
203218
placeholder="Sök på person eller kundnummer"
204219
contact={selectedContact}
205220
onSelect={setSelectedContact}
221+
inputRef={inputRef}
206222
/>
207223

208224
<Paper
@@ -213,7 +229,7 @@ const ResidencesPage: React.FC = () => {
213229
padding: '0px 20px',
214230
}}
215231
>
216-
<FormProvider {...formMethods} handleSubmit={handleSubmit}>
232+
<FormProvider {...formMethods}>
217233
<form onSubmit={handleSubmit(onSubmit)}>
218234
<fieldset disabled={!isSuccess}>
219235
<Grid container spacing={2} padding={2}>
@@ -234,7 +250,7 @@ const ResidencesPage: React.FC = () => {
234250

235251
<Divider />
236252

237-
<HousingReferenceReviewStatus />
253+
<ReviewStatusSection />
238254

239255
<CustomerReference
240256
customerReferenceReceivedAt={

packages/frontend/src/pages/Residences/components/ApplicantIsOther.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.

packages/frontend/src/pages/Residences/components/ApplicantIsOwner.tsx

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/frontend/src/pages/Residences/components/ApplicantIsRenter.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/frontend/src/pages/Residences/components/Form/ExpiresAt.tsx

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/frontend/src/pages/Residences/components/Form/HousingReferenceEmail.tsx

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)