Open wenchonglee opened 1 year ago
Certification
model?*I'll pretty much be ignoring the UI (e.g. CareerHistoryContent
and any styles in the form) and only looking at the core logic. We'll go through this more thoroughly internally
defaultValues: makeDefaultValues(selectedCareerValue, ["skills"])
(Actual code TBD) useForm
instances. I think in this file, we can just destructure useForm
normally instead of repeating careerFormMethods
over and overI hate to bring in leetcode mentality but this is kinda O(n^2) and will be difficult to understand when we work on more complicated records. https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/pages/EmployeeInfo/components/CareerHistoryForm/CareerHistoryForm.tsx#L108-L151
[].map
implicitly means you're making a new array (i.e. practicing immutability). I'd say you should use [].forEach
instead so others immediately know you're mutating something const submitFormHandler = careerFormMethods.handleSubmit(async (data) => {
const dataWithRef = transformData(data, {
string: ["key1", "key2"],
stringArray: ["key3"],
object: ["key4"]
});
//...
saveOrCreateCareer(dataWithRef)
})
This block of code for highlight
isn't simple enough for it not to be abstracted, it should be ideally be inferred
https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/pages/EmployeeInfo/components/CareerHistoryForm/CareerHistoryForm.tsx#L269-L274
The API of this component shouldn't include disabled
when it can be inferred. Though, after seeing the other instances like careerFormMethods.formState.dirtyFields.appointment?.position
makes me think this is more nuanced. I'm noting it down first before I get to ReferenceTrigger
.
https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/pages/EmployeeInfo/components/CareerHistoryForm/CareerHistoryForm.tsx#L284-L287
!
here, you can change the useContext
hook
https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/pages/EmployeeInfo/components/CareerHistoryForm/components/StringArrayInput/StringArrayInput.tsx#L34-L36// from
export const useReferenceStateContext = () => React.useContext(ReferenceStateContext);
// to
export const useReferenceStateContext = () => {
const context = React.useContext(ReferenceStateContext);
if(context === undefined) throw "Context must be used within provider";
return context;
}
If you want to be super type-safe, then you can opt to introduce your own provider
// from (having consumers invoke the context directly)
// where the value is typed to accept undefined
<ReferenceStateContext.Provider value={referenceStateMethods}>
// to exposing a helper (this is pseudo code)
// where you can enforce the type to not accept undefined
export const ReferenceStateProvider = ({ children, methods }: {children:ReactNode, methods: ReferencesStateMethods}) => {
return <ReferenceStateContext.Provider value={referenceStateMethods}>
{children}
</ReferenceStateContext.Provider>
}
You can keep it consistent and use field.value
https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/pages/EmployeeInfo/components/CareerHistoryForm/components/StringArrayInput/StringArrayInput.tsx#L55
I'm wondering if we could do away with generics and make certain assumptions, i.e. remove FieldArray<T, ArrayPath<T>> | FieldArray<T, ArrayPath<T>>[]
and such. While it certainly makes sense now, I think it doesn't contribute to type safety within the component. On the other hand, I think we should type the form context to be type-safer, something like:
// pseudo example
const { control, formState, getValues, setValue } = useFormContext<{
references: ReferenceType[]
} & FieldValues;
If I'm understanding this right, you can probably do it in 1 loop for this code block https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/pages/EmployeeInfo/components/CareerHistoryForm/components/StringArrayInput/StringArrayInput.tsx#L72-L78
let index = 0;
const reindexedRef = references.map(ref => {
if(ref.field === name) {
const newRef = {...ref, content: String(index)}
index +=1;
return newRef
}
return ref;
})
referenceTrigger
, I think you can move it in if you don't have a use case in mind const arrayErrors = errors[name] as unknown as { [key: string]: string }[]
this line confused me a bit, let's leave a comment for this internally and can change to FieldError[]
insteadhandleSubmit
instead, to leverage on rhf's validation
// from
const { updateReference } = useUpdateReferences({
source: sourceFormMethod.getValues(),
});
const handleMassApply = () => {
updateReference({ field, arrayId });
// ...redacted
};
// to const { updateReference } = useUpdateReferences(); const handleMassApply = handleSubmit((formData) => { updateReference({ source:formData, field, arrayId }); // ...redacted });
- Personally, I stop myself when there are 3 or more states I find that I need to wrangle, especially if I'm setting multiple states at the same time. I prefer using a reducer so that the intent becomes clear
- Let me know if you're unfamiliar with reducers and I can explain it to you. This isn't the most important thing here but I think it helps maintainability **if** we all have a good understanding of reducers
```ts
// from
const [openPanel, setOpenPanel] = React.useState(false);
const [currentField, setCurrentField] = React.useState<Path<CareerType> | Path<AppointmentType> | Path<CertificationType>
>();
const [currentArrayId, setCurrentArrayId] = React.useState<number>();
const [lastSource, setLastSource] = React.useState<SourceType>();
const [isMassApply, setIsMassApply] = React.useState(false);
// ...snippet of using it in panel.tsx
setOpenPanel(false);
setIsMassApply(false);
handleMassApplyingFields.setState([]);
// to (this is pseudo code I lifted elsewhere)
function reducer(state, action) {
switch (action.type) {
case 'RESET':
return INITIAL_STATE;
case 'OPEN_PANEL':
return {openPanel: true, currentField: action.currentField, currentArrId: action.arrId};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
// ...snippet of using it in panel.tsx
dispatch({ type:"RESET"});
// ...snippet of using it in trigger.tsx
const handlePanelOpen = () => dispatch({ type:"OPEN_PANEL", currentField, arrId });
I'll suggest we use immer
to kinda have the best of both worlds, even though I'm not very familiar with the library
const updatedForm = produce(formMethods.getValues(), draft => {
const { filteredReference } = getExistingReference({
formMethodValue: draft as any,
field,
arrayId
})
if (isAddSource) { // add new filteredReference.sources.push(source); } else if (isUpdateSource) { // update filteredReference.sources[sourceId] = source; } else if (isDeleteSource) { // delete filteredReference.sources.splice(sourceId, 1); } }) formMethods.reset(updatedForm )
edit: it seems the author of rhf has commented it is okay to use reset for this purpose. If you find any issues with reset, then we might have to consider rewriting this bit of logic to setValue
in the correct places
Form
Form Wrapper
We might want to consider consolidating
useLocalStorage
andpreventLeaving
into aconfig
object. But until we haveuseLocalStorage
complete, we can leave this as-is.No other comments
TextInput, TextArea, Dropdown, MultiSelect
I don't think there's a case for overriding
onChange
, I'd just omitonChange
andvalue
altogether. Then there's no need touseCallback
anymore. https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/components/Form/TextInput/TextInput.tsx#L19-L26With the change above, there isn't really a need to do this but for clarity sake, spread the
{...mantineTextInputProps}
beforevalue
andonChange
. We might even want to consider omittingerror
from the component prop type. https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/components/Form/TextInput/TextInput.tsx#L29-L34Chip
A lint rule we have is to prefix generics with
T
. https://typescript-eslint.io/rules/naming-convention/#enforce-that-type-parameters-generics-are-prefixed-with-tA rule of thumb I'm advocating for is:
TFieldValue
,TSelection
https://github.com/ZJTAN97/job_poc-form-component/blob/ccb05c50d0e2a07312c37309fa504bdcfd2cee6c/src/components/Form/ChipSelection/ChipSelection.tsx#L11
I think this component will need some design work if we're introducing it, so I won't give more comments than this for now.