Skip to content

Commit

Permalink
Fix dot keys messing up react-hook-form by reintroducing dot.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
0xNeshi committed Jan 15, 2024
1 parent 0b6519d commit 533d072
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Label } from "components/form";
import { isEmpty } from "helpers";
import { GENERIC_ERROR_MESSAGE } from "constants/common";
import createSchema from "./createSchema";
import { redot, undot } from "./dot";

type Props = {
fields: Group[];
Expand Down Expand Up @@ -50,8 +51,6 @@ export default function RecipientDetailsForm({

async function refresh() {
const { accountHolderName, bankStatement, ...details } = getValues();
console.log(getValues());

await updateRequirements({
quoteId,
amount,
Expand All @@ -62,7 +61,10 @@ export default function RecipientDetailsForm({
ownedByCustomer: false,
profile: "{{profileId}}",
type,
details,
details: Object.keys(details).reduce(
(prev, curr) => ({ ...prev, [redot(curr)]: details[curr] }),
{}
),
},
});
}
Expand Down Expand Up @@ -122,20 +124,21 @@ export default function RecipientDetailsForm({
>
{fields.map((f) => {
const labelRequired = f.required ? true : undefined;
const fName = undot(f.key);
if (f.type === "select") {
return (
<div key={`container-${f.key}`}>
<Label required={labelRequired} htmlFor={f.key} className="mb-1">
<div key={`container-${fName}`}>
<Label required={labelRequired} htmlFor={fName} className="mb-1">
{f.name}
</Label>
<select
{...register(f.key, {
{...register(fName, {
onChange: f.refreshRequirementsOnChange ? refresh : undefined,
shouldUnregister: true,
disabled: isSubmitting,
})}
aria-required={f.required}
id={f.key}
id={fName}
className="field-input"
>
{f.valuesAllowed?.map((v) => (
Expand All @@ -146,7 +149,7 @@ export default function RecipientDetailsForm({
</select>
<ErrorMessage
errors={errors}
name={f.key}
name={fName}
as="p"
className="text-red text-xs -mb-5"
/>
Expand All @@ -156,7 +159,7 @@ export default function RecipientDetailsForm({

if (f.type === "radio") {
return (
<div key={`container-${f.key}`} className="grid gap-1">
<div key={`container-${fName}`} className="grid gap-1">
<Label required={labelRequired} className="mb-1">
{f.name}
</Label>
Expand All @@ -171,7 +174,7 @@ export default function RecipientDetailsForm({
id={`radio__${v.key}`}
type="radio"
value={v.key}
{...register(f.key, {
{...register(fName, {
onChange: f.refreshRequirementsOnChange
? refresh
: undefined,
Expand All @@ -190,7 +193,7 @@ export default function RecipientDetailsForm({
</div>
<ErrorMessage
errors={errors}
name={f.key}
name={fName}
as="p"
className="text-red text-xs justify-self-end -mb-5"
/>
Expand All @@ -200,16 +203,16 @@ export default function RecipientDetailsForm({

if (f.type === "text") {
return (
<div key={`container-${f.key}`} className="grid gap-1">
<Label required={labelRequired} htmlFor={f.key}>
<div key={`container-${fName}`} className="grid gap-1">
<Label required={labelRequired} htmlFor={fName}>
{f.name}
</Label>
<input
className="field-input"
type="text"
id={f.key}
id={fName}
placeholder={f.example}
{...register(f.key, {
{...register(fName, {
//onBlur only as text input changes rapidly
onBlur: f.refreshRequirementsOnChange ? refresh : undefined,
shouldUnregister: true,
Expand All @@ -218,7 +221,7 @@ export default function RecipientDetailsForm({
/>
<ErrorMessage
errors={errors}
name={f.key}
name={fName}
as="p"
className="text-red text-xs justify-self-end -mb-5"
/>
Expand All @@ -228,24 +231,24 @@ export default function RecipientDetailsForm({

if (f.type === "date") {
return (
<div key={`container-${f.key}`} className="grid gap-1">
<Label required={labelRequired} htmlFor={f.key}>
<div key={`container-${fName}`} className="grid gap-1">
<Label required={labelRequired} htmlFor={fName}>
{f.name}
</Label>
<input
className="w-full p-3 rounded border border-prim"
type="text"
key={f.key}
key={fName}
placeholder={f.example}
{...register(f.key, {
{...register(fName, {
onBlur: f.refreshRequirementsOnChange ? refresh : undefined,
shouldUnregister: true,
disabled: isSubmitting,
})}
/>
<ErrorMessage
errors={errors}
name={f.key}
name={fName}
as="p"
className="text-red text-xs justify-self-end -mb-5"
/>
Expand All @@ -254,7 +257,7 @@ export default function RecipientDetailsForm({
}

return (
<div className="bg-red" key={f.key}>
<div className="bg-red" key={fName}>
{f.name}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ApplicationMIMEType } from "types/lists";
import { logger } from "helpers";
import { requiredString } from "schemas/string";
import { BYTES_IN_MB } from "constants/common";
import { undot } from "./dot";

const VALID_TYPE: ApplicationMIMEType = "application/pdf";
const MB_LIMIT = 6;
Expand Down Expand Up @@ -58,7 +59,8 @@ export default function createSchema(fields: Group[]) {
);
}
}
schema[field.key] = fSchema;

schema[undot(field.key)] = fSchema;
return schema;
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const DOT = ".";
const DOLLAR_SIGN = "$";

/**
* `react-hook-form` turns dot-field names into nested object fields, causing weird behavior when
* accessing/assigning said fields if they're deeply nested.
*
* E.g. with an object like:
* ```
* type FormValues = {
* requirements: {
* type: {
* [key: string]: any
* }
* }
* }
* ```
* setting a field named `address.country` inside `requirements.type` can internally be converted into:
* ```
* const formValues: FormValues = {
* requirements: {
* type:
* address: {
* country: "some_value"
* }
* }
* }
* }
* ```
* This is the default behavior, see:
* - https://github.com/react-hook-form/react-hook-form/issues/3351#issuecomment-721996499
* - https://react-hook-form.com/docs/useform/register
*
* The problem is that it is then difficult to access this `address.country` field, as `react-hook-form`
* gets confused and has no idea how to access the field:
* ```
* console.log(methods.getValues("requirements.type.address.country")) // returns undefined
* ```
*
* To solve, turn dots into some other character when creating the form, see:
* https://github.com/react-hook-form/react-hook-form/issues/3755#issuecomment-943408807
*
* @param key string
* @returns same string with dot characters (.) replaced with dollar signs ($)
* */
export const undot = (key: string): string => key.replace(DOT, DOLLAR_SIGN);

/**
* @see {@link undot}
* @param key string
* @returns same string with dollar signs ($) replaced with dot characters (.)
*/
export const redot = (key: string): string => key.replace(DOLLAR_SIGN, DOT);

0 comments on commit 533d072

Please sign in to comment.