Last active
December 1, 2021 15:27
-
-
Save ericzakariasson/3bb9abc9d0b7f65cd873573cac36cb38 to your computer and use it in GitHub Desktop.
useArray
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
import { act, renderHook } from "@testing-library/react-hooks"; | |
import { useArray } from "./useArray"; | |
describe("useArray", () => { | |
describe("props", () => { | |
it("should have initial state", () => { | |
const { result } = renderHook(() => | |
useArray({ | |
initialState: ["a", "b", "c"], | |
selector: (x) => x, | |
}) | |
); | |
const [array] = result.current; | |
expect(array).toMatchInlineSnapshot(` | |
Array [ | |
"a", | |
"b", | |
"c", | |
] | |
`); | |
}); | |
}); | |
describe("add", () => { | |
it("should add item ", () => { | |
const { result } = renderHook(() => | |
useArray<string>({ selector: (x) => x }) | |
); | |
expect(result.current[0]).toHaveLength(0); | |
act(() => { | |
result.current[1].add("a"); | |
}); | |
expect(result.current[0]).toHaveLength(1); | |
expect(result.current[0][0]).toBe("a"); | |
}); | |
}); | |
describe("remove", () => { | |
it("should remove primitive item ", () => { | |
const { result } = renderHook(() => | |
useArray<string>({ | |
initialState: ["a"], | |
selector: (x) => x, | |
}) | |
); | |
expect(result.current[0]).toHaveLength(1); | |
act(() => { | |
result.current[1].remove("a"); | |
}); | |
expect(result.current[0]).toHaveLength(0); | |
}); | |
it("should remove complex item ", () => { | |
const { result } = renderHook(() => | |
useArray<{ foo: string }>({ | |
initialState: [{ foo: "bar" }], | |
selector: (x) => x.foo, | |
}) | |
); | |
expect(result.current[0]).toHaveLength(1); | |
act(() => { | |
result.current[1].remove({ foo: "bar" }); | |
}); | |
expect(result.current[0]).toHaveLength(0); | |
}); | |
}); | |
describe("has", () => { | |
it("should yield true for primitive items", () => { | |
const { result } = renderHook(() => | |
useArray<string>({ | |
initialState: ["a"], | |
selector: (x) => x, | |
}) | |
); | |
const [, { has }] = result.current; | |
expect(has("a")).toBe(true); | |
}); | |
it("should yield false for primitive items ", () => { | |
const { result } = renderHook(() => | |
useArray<string>({ | |
initialState: ["a"], | |
selector: (x) => x, | |
}) | |
); | |
const [, { has }] = result.current; | |
expect(has("b")).toBe(false); | |
}); | |
it("should yield true for complex items", () => { | |
const { result } = renderHook(() => | |
useArray({ | |
initialState: [{ foo: { bar: "baz" } }], | |
selector: (x) => x.foo.bar, | |
}) | |
); | |
const [, { has }] = result.current; | |
expect(has({ foo: { bar: "baz" } })).toBe(true); | |
}); | |
it("should yield false for complex items ", () => { | |
const { result } = renderHook(() => | |
useArray({ | |
initialState: [{ foo: { bar: "baz" } }], | |
selector: (x) => x.foo.bar, | |
}) | |
); | |
const [, { has }] = result.current; | |
expect(has({ foo: { bar: "foo" } })).toBe(false); | |
}); | |
}); | |
describe("reset", () => { | |
it("should reset to initial state", () => { | |
const { result } = renderHook(() => | |
useArray({ | |
initialState: ["a"], | |
selector: (x) => x, | |
}) | |
); | |
expect(result.current[0]).toHaveLength(1); | |
act(() => { | |
result.current[1].add("b"); | |
result.current[1].add("c"); | |
}); | |
expect(result.current[0]).toHaveLength(3); | |
act(() => { | |
result.current[1].reset(); | |
}); | |
expect(result.current[0]).toHaveLength(1); | |
expect(result.current[0][0]).toBe("a"); | |
}); | |
}); | |
describe("clear", () => { | |
it("should clear state", () => { | |
const { result } = renderHook(() => | |
useArray<string>({ | |
initialState: ["a", "b", "c"], | |
selector: (x) => x, | |
}) | |
); | |
expect(result.current[0]).toHaveLength(3); | |
act(() => { | |
result.current[1].clear(); | |
}); | |
expect(result.current[0]).toHaveLength(0); | |
}); | |
}); | |
describe("set", () => { | |
it("should set state", () => { | |
const { result } = renderHook(() => | |
useArray<string>({ | |
initialState: ["a", "b", "c"], | |
selector: (x) => x, | |
}) | |
); | |
expect(result.current[0]).toMatchInlineSnapshot(` | |
Array [ | |
"a", | |
"b", | |
"c", | |
] | |
`); | |
act(() => { | |
result.current[1].set(["d", "e", "f"]); | |
}); | |
expect(result.current[0]).toMatchInlineSnapshot(` | |
Array [ | |
"d", | |
"e", | |
"f", | |
] | |
`); | |
}); | |
}); | |
}); |
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
import { useState } from "react"; | |
type Primitive = string | number | boolean; | |
type Selector<T> = (item: T) => Primitive; | |
const isPrimitive = (value: unknown): value is Primitive => { | |
return ["string", "number", "boolean"].includes(typeof value); | |
}; | |
const primitiveSelector: Selector<unknown> = (item) => { | |
if (!isPrimitive(item)) { | |
throw new Error(`${typeof item} is not a primitive type`); | |
} | |
return item; | |
}; | |
const getArrayHelpers = <T>(selector: Selector<T>) => ({ | |
add: (array: T[], item: T) => array.concat(item), | |
remove: (array: T[], item: T) => | |
array.filter((i) => selector(i) !== selector(item)), | |
has: (array: T[], item: T) => | |
array.some((i) => selector(i) === selector(item)), | |
toggle: (array: T[], item: T) => { | |
const helpers = getArrayHelpers(selector); | |
if (helpers.has(array, item)) { | |
return helpers.remove(array, item); | |
} | |
return helpers.add(array, item); | |
}, | |
}); | |
type UseArrayProps<T> = { | |
initialState?: T[]; | |
selector?: T extends Primitive ? never : Selector<T>; | |
}; | |
type UseArrayActions<T> = { | |
add: (item: T) => void; | |
remove: (item: T) => void; | |
has: (item: T) => boolean; | |
set: (items: T[]) => void; | |
toggle: (item: T) => void; | |
reset: () => void; | |
clear: () => void; | |
}; | |
type UseArrayValue<T> = [T[], UseArrayActions<T>]; | |
export const useArray = <T>({ | |
initialState, | |
selector: customSelector, | |
}: UseArrayProps<T> = {}): UseArrayValue<T> => { | |
const [array, setArray] = useState<T[]>(initialState ?? []); | |
const selector = customSelector ?? primitiveSelector; | |
const helpers = getArrayHelpers(selector); | |
const add = (item: T) => setArray((prev) => helpers.add(prev, item)); | |
const remove = (item: T) => setArray((prev) => helpers.remove(prev, item)); | |
const has = (item: T) => helpers.has(array, item); | |
const toggle = (item: T) => setArray((prev) => helpers.toggle(prev, item)); | |
const reset = () => setArray(initialState ?? []); | |
const clear = () => setArray([]); | |
const set = (items: T[]) => setArray(items); | |
return [array, { add, remove, has, toggle, reset, clear, set }]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment