-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(listAtom): support scoped names of nested fields (#110)
* test(listAtom): scoped names * listItem as extended formAtom with name * use extended listItemForm * fix types so build passes * add effect for syncing field names * docs(List): add names
- Loading branch information
1 parent
d780fb2
commit f8201da
Showing
9 changed files
with
380 additions
and
65 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,22 +1,34 @@ | ||
import { FieldAtom } from "form-atoms"; | ||
import { Atom, atom } from "jotai"; | ||
import { Atom, Getter, atom } from "jotai"; | ||
|
||
export type ExtendFieldAtom<Value, State> = | ||
FieldAtom<Value> extends Atom<infer DefaultState> | ||
? Atom<DefaultState & State> | ||
: never; | ||
|
||
export const extendFieldAtom = < | ||
T extends FieldAtom<any>, | ||
T extends Atom<any>, | ||
E extends Record<string, unknown>, | ||
>( | ||
field: T, | ||
makeAtoms: (cfg: T extends Atom<infer Config> ? Config : never) => E, | ||
makeAtoms: ( | ||
cfg: T extends Atom<infer Config> ? Config : never, | ||
get: Getter, | ||
) => E, | ||
) => | ||
atom((get) => { | ||
const base = get(field); | ||
return { | ||
...base, | ||
...makeAtoms(base as T extends Atom<infer Config> ? Config : never), | ||
}; | ||
}); | ||
atom( | ||
(get) => { | ||
const base = get(field); | ||
return { | ||
...base, | ||
...makeAtoms( | ||
base as T extends Atom<infer Config> ? Config : never, | ||
get, | ||
), | ||
}; | ||
}, | ||
(get, set, update: T extends Atom<infer Config> ? Config : never) => { | ||
// @ts-expect-error fieldAtom is PrimitiveAtom | ||
set(field, { ...get(field), ...update }); | ||
}, | ||
); |
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 |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { act, renderHook } from "@testing-library/react"; | ||
import { act, renderHook, waitFor } from "@testing-library/react"; | ||
import { | ||
FieldAtom, | ||
formAtom, | ||
useFieldActions, | ||
useFieldErrors, | ||
|
@@ -13,7 +14,7 @@ import { describe, expect, it, test, vi } from "vitest"; | |
|
||
import { listAtom } from "./listAtom"; | ||
import { numberField, textField } from "../../fields"; | ||
import { useFieldError, useListActions } from "../../hooks"; | ||
import { useFieldError, useListActions, useListField } from "../../hooks"; | ||
|
||
describe("listAtom()", () => { | ||
test("can be submitted within formAtom", async () => { | ||
|
@@ -370,4 +371,108 @@ describe("listAtom()", () => { | |
expect(state.current.dirty).toBe(false); | ||
}); | ||
}); | ||
|
||
describe("scoped name of list fields", () => { | ||
const useFieldName = <T extends FieldAtom<any>>(fieldAtom: T) => | ||
useAtomValue(useAtomValue(fieldAtom).name); | ||
|
||
describe("list of primitive fieldAtoms", () => { | ||
it("field name contains list name and index", async () => { | ||
const field = listAtom({ | ||
name: "recipients", | ||
value: ["[email protected]", "[email protected]"], | ||
builder: (value) => textField({ value }), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: names } = renderHook(() => [ | ||
useFieldName(list.current.items[0]!.fields), | ||
useFieldName(list.current.items[1]!.fields), | ||
]); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(names.current).toEqual(["recipients[0]", "recipients[1]"]); | ||
}); | ||
}); | ||
|
||
describe("list of form fields", () => { | ||
it("field name contains list name, index and field name", async () => { | ||
const field = listAtom({ | ||
name: "contacts", | ||
value: [{ email: "[email protected]" }, { email: "[email protected]" }], | ||
builder: ({ email }) => ({ | ||
email: textField({ value: email, name: "email" }), | ||
}), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: names } = renderHook(() => [ | ||
useFieldName(list.current.items[0]!.fields.email), | ||
useFieldName(list.current.items[1]!.fields.email), | ||
]); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(names.current).toEqual([ | ||
"contacts[0].email", | ||
"contacts[1].email", | ||
]); | ||
}); | ||
}); | ||
|
||
describe("nested listAtom", () => { | ||
// passes but throws error | ||
it.skip("has prefix of the parent listAtom", async () => { | ||
const field = listAtom({ | ||
name: "contacts", | ||
value: [ | ||
{ | ||
email: "[email protected]", | ||
addresses: [{ type: "home", city: "Kezmarok" }], | ||
}, | ||
{ | ||
email: "[email protected]", | ||
addresses: [ | ||
{ type: "home", city: "Humenne" }, | ||
{ type: "work", city: "Nove Zamky" }, | ||
], | ||
}, | ||
], | ||
builder: ({ email, addresses = [] }) => ({ | ||
email: textField({ value: email, name: "email" }), | ||
addresses: listAtom({ | ||
name: "addresses", | ||
value: addresses, | ||
builder: ({ type, city }) => ({ | ||
type: textField({ value: type, name: "type" }), | ||
city: textField({ value: city, name: "city" }), | ||
}), | ||
}), | ||
}), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: secondContactAddresses } = renderHook(() => | ||
useListField(list.current.items[1]!.fields.addresses), | ||
); | ||
|
||
const { result: names } = renderHook(() => [ | ||
useFieldName(secondContactAddresses.current.items[0]!.fields.type), | ||
useFieldName(secondContactAddresses.current.items[0]!.fields.city), | ||
useFieldName(secondContactAddresses.current.items[1]!.fields.type), | ||
useFieldName(secondContactAddresses.current.items[1]!.fields.city), | ||
]); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(names.current).toEqual([ | ||
"contacts[1].addresses[0].type", | ||
"contacts[1].addresses[0].city", | ||
"contacts[1].addresses[1].type", | ||
"contacts[1].addresses[1].city", | ||
]); | ||
}); | ||
}); | ||
}); | ||
}); |
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
Oops, something went wrong.