@boxcustodia/library 2.0.0-alpha.10 → 2.0.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +70 -70
- package/dist/index.css +2 -0
- package/dist/index.d.ts +420 -272
- package/dist/index.es.js +34448 -27816
- package/dist/theme.css +1 -1
- package/package.json +11 -6
- package/src/__doc__/Changelog.mdx +6 -0
- package/src/__doc__/Components.mdx +73 -0
- package/src/__doc__/Examples.tsx +69 -0
- package/src/__doc__/Icons.mdx +41 -0
- package/src/__doc__/Intro.mdx +138 -0
- package/src/__doc__/MCP.mdx +71 -0
- package/src/__doc__/Migration.mdx +475 -0
- package/src/__doc__/Theme.mdx +132 -0
- package/src/__doc__/Types.mdx +252 -0
- package/src/components/alert/alert.stories.tsx +142 -0
- package/src/components/alert/alert.tsx +109 -0
- package/src/components/alert/index.ts +7 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
- package/src/components/alert-dialog/alert-dialog.tsx +265 -0
- package/src/components/alert-dialog/index.ts +1 -0
- package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
- package/src/components/auto-complete/auto-complete.tsx +82 -0
- package/src/components/auto-complete/index.ts +2 -0
- package/src/components/avatar/avatar.stories.tsx +84 -0
- package/src/components/avatar/avatar.test.tsx +61 -0
- package/src/components/avatar/avatar.tsx +104 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/background-image/background-image.stories.tsx +21 -0
- package/src/components/background-image/background-image.test.tsx +29 -0
- package/src/components/background-image/background-image.tsx +23 -0
- package/src/components/background-image/index.ts +1 -0
- package/src/components/button/button.stories.tsx +396 -0
- package/src/components/button/button.test.tsx +58 -0
- package/src/components/button/button.tsx +31 -0
- package/src/components/button/button.variants.ts +44 -0
- package/src/components/button/components/base-button.tsx +86 -0
- package/src/components/button/components/loader-overlay.tsx +21 -0
- package/src/components/button/components/loading-icon.tsx +47 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/calendar/calendar.model.ts +86 -0
- package/src/components/calendar/calendar.stories.tsx +155 -0
- package/src/components/calendar/calendar.test.tsx +12 -0
- package/src/components/calendar/calendar.tsx +185 -0
- package/src/components/calendar/components/calendar-navigation.tsx +141 -0
- package/src/components/calendar/components/day.tsx +61 -0
- package/src/components/calendar/components/decade-view.tsx +45 -0
- package/src/components/calendar/components/index.ts +6 -0
- package/src/components/calendar/components/month-view.tsx +58 -0
- package/src/components/calendar/components/week-days.tsx +27 -0
- package/src/components/calendar/components/year-view.tsx +29 -0
- package/src/components/calendar/hooks/index.ts +4 -0
- package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
- package/src/components/calendar/hooks/use-calendar.ts +90 -0
- package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
- package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
- package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/calendar/utils/typeguards.ts +7 -0
- package/src/components/card/card.stories.tsx +116 -0
- package/src/components/card/card.tsx +74 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/center/center.stories.tsx +81 -0
- package/src/components/center/center.tsx +24 -0
- package/src/components/center/index.ts +1 -0
- package/src/components/checkbox/checkbox.stories.tsx +307 -0
- package/src/components/checkbox/checkbox.tsx +273 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
- package/src/components/checkbox-group/checkbox-group.tsx +16 -0
- package/src/components/checkbox-group/index.ts +1 -0
- package/src/components/combobox/combobox.stories.tsx +339 -0
- package/src/components/combobox/combobox.tsx +892 -0
- package/src/components/combobox/index.ts +1 -0
- package/src/components/date-picker/date-input.stories.tsx +158 -0
- package/src/components/date-picker/date-input.tsx +163 -0
- package/src/components/date-picker/date-picker.model.ts +90 -0
- package/src/components/date-picker/date-picker.stories.tsx +200 -0
- package/src/components/date-picker/date-picker.test.tsx +23 -0
- package/src/components/date-picker/date-picker.tsx +298 -0
- package/src/components/date-picker/date-picker.utils.ts +260 -0
- package/src/components/date-picker/index.ts +3 -0
- package/src/components/date-picker/use-date-input-popover.ts +48 -0
- package/src/components/date-picker/use-date-input.ts +125 -0
- package/src/components/dialog/dialog.stories.tsx +171 -0
- package/src/components/dialog/dialog.test.tsx +68 -0
- package/src/components/dialog/dialog.tsx +277 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/divider/divider.stories.tsx +70 -0
- package/src/components/divider/divider.test.tsx +22 -0
- package/src/components/divider/divider.tsx +23 -0
- package/src/components/divider/index.ts +1 -0
- package/src/components/dropzone/dropzone.stories.tsx +210 -0
- package/src/components/dropzone/dropzone.tsx +154 -0
- package/src/components/dropzone/file-types.ts +64 -0
- package/src/components/dropzone/index.ts +3 -0
- package/src/components/dropzone/upload-primitives.tsx +310 -0
- package/src/components/dropzone/use-dropzone.ts +122 -0
- package/src/components/empty-state/empty-state.stories.tsx +56 -0
- package/src/components/empty-state/empty-state.tsx +39 -0
- package/src/components/empty-state/index.ts +1 -0
- package/src/components/field/field.stories.tsx +223 -0
- package/src/components/field/field.tsx +229 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/form/form.stories.tsx +594 -0
- package/src/components/form/form.tsx +30 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/heading/heading.stories.tsx +74 -0
- package/src/components/heading/heading.tsx +28 -0
- package/src/components/heading/heading.variants.ts +27 -0
- package/src/components/heading/index.ts +1 -0
- package/src/components/index.ts +46 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.stories.tsx +104 -0
- package/src/components/input/input.tsx +75 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.stories.tsx +40 -0
- package/src/components/kbd/kbd.tsx +31 -0
- package/src/components/kbd/kbd.variants.ts +26 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.stories.tsx +68 -0
- package/src/components/label/label.test.tsx +61 -0
- package/src/components/label/label.tsx +62 -0
- package/src/components/loader/index.ts +1 -0
- package/src/components/loader/loader.stories.tsx +60 -0
- package/src/components/loader/loader.test.tsx +26 -0
- package/src/components/loader/loader.tsx +60 -0
- package/src/components/menu/index.ts +2 -0
- package/src/components/menu/menu-primitives.tsx +248 -0
- package/src/components/menu/menu.stories.tsx +203 -0
- package/src/components/menu/menu.tsx +100 -0
- package/src/components/menu/util/render-menu-item.tsx +54 -0
- package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
- package/src/components/multi-select/index.ts +1 -0
- package/src/components/multi-select/multi-select.stories.tsx +294 -0
- package/src/components/multi-select/multi-select.tsx +300 -0
- package/src/components/multi-select/multi-select.variants.ts +22 -0
- package/src/components/number-input/index.ts +1 -0
- package/src/components/number-input/number-input.stories.tsx +209 -0
- package/src/components/number-input/number-input.test.tsx +87 -0
- package/src/components/number-input/number-input.tsx +230 -0
- package/src/components/pagination/components/pagination-option.tsx +27 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.stories.tsx +80 -0
- package/src/components/pagination/pagination.test.tsx +76 -0
- package/src/components/pagination/pagination.tsx +102 -0
- package/src/components/password/index.ts +1 -0
- package/src/components/password/password.stories.tsx +104 -0
- package/src/components/password/password.tsx +71 -0
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.stories.tsx +213 -0
- package/src/components/popover/popover.tsx +203 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/progress/progress.stories.tsx +124 -0
- package/src/components/progress/progress.test.tsx +25 -0
- package/src/components/progress/progress.tsx +124 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
- package/src/components/scroll-area/scroll-area.tsx +64 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.stories.tsx +253 -0
- package/src/components/select/select.tsx +430 -0
- package/src/components/show/index.ts +1 -0
- package/src/components/show/show.stories.tsx +197 -0
- package/src/components/show/show.test.tsx +41 -0
- package/src/components/show/show.tsx +16 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.stories.tsx +36 -0
- package/src/components/skeleton/skeleton.test.tsx +14 -0
- package/src/components/skeleton/skeleton.tsx +15 -0
- package/src/components/stack/index.ts +1 -0
- package/src/components/stack/stack.stories.tsx +194 -0
- package/src/components/stack/stack.tsx +52 -0
- package/src/components/stepper/Stepper.tsx +190 -0
- package/src/components/stepper/context/stepper-context.tsx +11 -0
- package/src/components/stepper/index.ts +1 -0
- package/src/components/stepper/stepper.stories.tsx +130 -0
- package/src/components/stepper/stepper.test.tsx +91 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.stories.tsx +122 -0
- package/src/components/switch/switch.test.tsx +30 -0
- package/src/components/switch/switch.tsx +86 -0
- package/src/components/table/index.ts +3 -0
- package/src/components/table/table-primitives.tsx +122 -0
- package/src/components/table/table.model.ts +20 -0
- package/src/components/table/table.stories.tsx +169 -0
- package/src/components/table/table.test.tsx +91 -0
- package/src/components/table/table.tsx +109 -0
- package/src/components/table-pagination/index.ts +2 -0
- package/src/components/table-pagination/table-pagination.model.ts +2 -0
- package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
- package/src/components/table-pagination/table-pagination.test.tsx +32 -0
- package/src/components/table-pagination/table-pagination.tsx +108 -0
- package/src/components/tabs/context/tabs-context.tsx +14 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +182 -0
- package/src/components/tabs/tabs.test.tsx +61 -0
- package/src/components/tabs/tabs.tsx +175 -0
- package/src/components/tag/index.ts +2 -0
- package/src/components/tag/tag.stories.tsx +170 -0
- package/src/components/tag/tag.test.tsx +18 -0
- package/src/components/tag/tag.tsx +99 -0
- package/src/components/tag/tag.variants.ts +31 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.stories.tsx +73 -0
- package/src/components/textarea/textarea.tsx +105 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/timeline-status.ts +5 -0
- package/src/components/timeline/timeline.stories.tsx +84 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast.stories.tsx +392 -0
- package/src/components/toast/toast.test.tsx +50 -0
- package/src/components/toast/toast.tsx +411 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.stories.tsx +226 -0
- package/src/components/tooltip/tooltip.test.tsx +46 -0
- package/src/components/tooltip/tooltip.tsx +171 -0
- package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
- package/src/components/tree/index.ts +2 -0
- package/src/components/tree/tree-primitives.tsx +126 -0
- package/src/components/tree/tree.stories.tsx +468 -0
- package/src/components/tree/tree.tsx +42 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
- package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
- package/src/hooks/useArray/index.ts +1 -0
- package/src/hooks/useArray/useArray.ts +76 -0
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
- package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
- package/src/hooks/useAsync/index.ts +1 -0
- package/src/hooks/useAsync/useAsync.ts +58 -0
- package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
- package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
- package/src/hooks/useClickOutside/index.ts +1 -0
- package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
- package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
- package/src/hooks/useClipboard/index.ts +1 -0
- package/src/hooks/useClipboard/useClipboard.tsx +28 -0
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
- package/src/hooks/useDebounceCallback/index.ts +1 -0
- package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
- package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
- package/src/hooks/useDebounceValue/index.ts +1 -0
- package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
- package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
- package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
- package/src/hooks/useDisclosure/index.ts +1 -0
- package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
- package/src/hooks/useDocumentTitle/index.ts +1 -0
- package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
- package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
- package/src/hooks/useEventListener/index.ts +1 -0
- package/src/hooks/useEventListener/useEventListener.ts +25 -0
- package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
- package/src/hooks/useFocusTrap/index.ts +1 -0
- package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
- package/src/hooks/useFocusTrap/tabbable.ts +70 -0
- package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
- package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
- package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
- package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
- package/src/hooks/useHotkey/__utils__/index.ts +3 -0
- package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
- package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
- package/src/hooks/useHotkey/index.ts +1 -0
- package/src/hooks/useHotkey/useHotkey.ts +34 -0
- package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
- package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
- package/src/hooks/useHover/index.ts +1 -0
- package/src/hooks/useHover/useHover.tsx +40 -0
- package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
- package/src/hooks/useIsVisible/index.ts +1 -0
- package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
- package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
- package/src/hooks/useLocalStorage/index.ts +1 -0
- package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
- package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
- package/src/hooks/useMediaQuery/index.ts +1 -0
- package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
- package/src/hooks/useMemoizedFn/index.ts +1 -0
- package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
- package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
- package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
- package/src/hooks/useMutation/index.ts +1 -0
- package/src/hooks/useMutation/useMutation.tsx +60 -0
- package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
- package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
- package/src/hooks/useObject/index.ts +1 -0
- package/src/hooks/useObject/useObject.tsx +48 -0
- package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
- package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
- package/src/hooks/usePagination/index.ts +2 -0
- package/src/hooks/usePagination/usePagination.tsx +74 -0
- package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
- package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
- package/src/hooks/usePortal/index.ts +1 -0
- package/src/hooks/usePortal/usePortal.ts +40 -0
- package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
- package/src/hooks/usePreventCloseWindow/index.ts +1 -0
- package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
- package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
- package/src/hooks/useRangePagination/index.ts +2 -0
- package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
- package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
- package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
- package/src/hooks/useSelection/index.ts +1 -0
- package/src/hooks/useSelection/useSelection.ts +121 -0
- package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
- package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
- package/src/hooks/useStep/index.ts +1 -0
- package/src/hooks/useStep/useStep.ts +57 -0
- package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
- package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
- package/src/hooks/useToggle/index.ts +1 -0
- package/src/hooks/useToggle/useToggle.ts +16 -0
- package/src/index.ts +6 -0
- package/src/lib/cn.ts +8 -0
- package/src/lib/index.ts +1 -0
- package/src/models/Generic.model.ts +67 -0
- package/src/models/index.ts +1 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/library-provider.tsx +44 -0
- package/src/providers/theme/ThemeProvider.tsx +25 -0
- package/src/providers/theme/index.ts +3 -0
- package/src/providers/theme/types.ts +11 -0
- package/src/providers/theme/useThemeProps.ts +25 -0
- package/src/stores/theme.store.ts +31 -0
- package/src/styles/components.css +4 -0
- package/src/styles/index.css +2 -0
- package/src/styles/library.css +2 -0
- package/src/styles/theme.css +232 -0
- package/src/utils/dates/parseDateRange.utility.ts +39 -0
- package/src/utils/form.tsx +91 -0
- package/src/utils/functions/createSafeContext.ts +17 -0
- package/src/utils/functions/ensureReactElement.tsx +30 -0
- package/src/utils/functions/getFormData.ts +19 -0
- package/src/utils/functions/index.ts +4 -0
- package/src/utils/functions/mergeRefs.ts +18 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/strings/extractInitials.utility.ts +10 -0
- package/src/utils/strings/index.ts +1 -0
- package/src/utils/tests/click.ts +3 -0
- package/src/utils/tests/index.ts +2 -0
- package/src/utils/tests/keyboard.ts +21 -0
- package/src/utils/tests/type.ts +6 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react-vite";
|
|
2
|
+
import { Trash } from "lucide-react";
|
|
3
|
+
import { Button, Input } from "../../../components";
|
|
4
|
+
import useLocalStorage from "../useLocalStorage";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook que proporciona una interfaz sencilla para interactuar con el localStorage de manera tipada.
|
|
8
|
+
*
|
|
9
|
+
* Este hook permite almacenar, recuperar y eliminar valores en el localStorage de manera segura,
|
|
10
|
+
* utilizando una API genérica que asegura la consistencia de tipos en TypeScript. Es compatible con cualquier
|
|
11
|
+
* tipo de dato, desde strings hasta objetos complejos.
|
|
12
|
+
*
|
|
13
|
+
* @param key - La clave del localStorage donde se almacenará el valor.
|
|
14
|
+
* @param initialValue - El valor inicial que se utilizará si no hay datos almacenados bajo la clave proporcionada.
|
|
15
|
+
* @returns Una tupla con el valor almacenado, una función para actualizarlo y otra para eliminarlo.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const [name, setName, removeName] = useLocalStorage<string>("name", "John Doe");
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const [count, setCount, removeCount] = useLocalStorage<number>("count", 0);
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const [items, setItems, removeItems] = useLocalStorage<string[]>("items", ["apple", "banana"]);
|
|
25
|
+
*/
|
|
26
|
+
const meta: Meta = {
|
|
27
|
+
title: "hooks/useLocalStorage",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
|
|
32
|
+
export const Default = {
|
|
33
|
+
render: () => {
|
|
34
|
+
const [value, setValue, removeValue] = useLocalStorage<string>("myKey", "");
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="space-y-2">
|
|
38
|
+
<Input value={value} onChange={setValue} />
|
|
39
|
+
<Button onClick={removeValue}>Clear</Button>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const WithNumber = {
|
|
46
|
+
render: () => {
|
|
47
|
+
const [count, setCount, removeCount] = useLocalStorage<number>("count", 0);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
<p>Count: {count}</p>
|
|
52
|
+
<div className="flex gap-2">
|
|
53
|
+
<Button onClick={() => setCount(count + 1)}>Increment</Button>
|
|
54
|
+
<Button onClick={removeCount}>Reset</Button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const WithArray = {
|
|
62
|
+
render: () => {
|
|
63
|
+
const [items, setItems, removeItems] = useLocalStorage<string[]>(
|
|
64
|
+
"oranges",
|
|
65
|
+
["🍊"],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="space-y-2">
|
|
70
|
+
<ul>
|
|
71
|
+
{items.map((item, index) => (
|
|
72
|
+
<li key={index}>{item}</li>
|
|
73
|
+
))}
|
|
74
|
+
</ul>
|
|
75
|
+
<div className="flex gap-2">
|
|
76
|
+
<Button onClick={() => setItems([...items, "🍊"])}>
|
|
77
|
+
🍊 Add Orange
|
|
78
|
+
</Button>
|
|
79
|
+
<Button onClick={removeItems}>
|
|
80
|
+
<Trash /> Clear List
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import useLocalStorage from "../useLocalStorage";
|
|
4
|
+
|
|
5
|
+
describe("useLocalStorage hook", () => {
|
|
6
|
+
const key = "testKey";
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
localStorage.clear();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should be defined", () => {
|
|
13
|
+
expect(useLocalStorage).toBeDefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should initialize with default value if localStorage is empty", () => {
|
|
17
|
+
const { result } = renderHook(() =>
|
|
18
|
+
useLocalStorage<string>(key, "initialValue"),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const [storedValue] = result.current;
|
|
22
|
+
expect(storedValue).toBe("initialValue");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should retrieve stored value from localStorage", () => {
|
|
26
|
+
localStorage.setItem(key, JSON.stringify("storedValue"));
|
|
27
|
+
|
|
28
|
+
const { result } = renderHook(() =>
|
|
29
|
+
useLocalStorage<string>(key, "initialValue"),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const [storedValue] = result.current;
|
|
33
|
+
expect(storedValue).toBe("storedValue");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should update localStorage when value changes", () => {
|
|
37
|
+
const { result } = renderHook(() =>
|
|
38
|
+
useLocalStorage<string>(key, "initialValue"),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const [, setValue] = result.current;
|
|
42
|
+
|
|
43
|
+
act(() => {
|
|
44
|
+
setValue("newValue");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const [storedValue] = result.current;
|
|
48
|
+
expect(storedValue).toBe("newValue");
|
|
49
|
+
expect(localStorage.getItem(key)).toBe(JSON.stringify("newValue"));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should remove the value from localStorage when remove is called", () => {
|
|
53
|
+
localStorage.setItem(key, JSON.stringify("storedValue"));
|
|
54
|
+
|
|
55
|
+
const { result } = renderHook(() => useLocalStorage<string>(key, ""));
|
|
56
|
+
|
|
57
|
+
const [, , remove] = result.current;
|
|
58
|
+
|
|
59
|
+
act(() => {
|
|
60
|
+
remove();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const [storedValue] = result.current;
|
|
64
|
+
expect(storedValue).toBe("");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should handle non-JSON serializable values gracefully", () => {
|
|
68
|
+
const consoleErrorSpy = vi
|
|
69
|
+
.spyOn(console, "error")
|
|
70
|
+
.mockImplementation(() => {
|
|
71
|
+
console.log("error");
|
|
72
|
+
});
|
|
73
|
+
localStorage.setItem(key, "non-JSON-string");
|
|
74
|
+
|
|
75
|
+
const { result } = renderHook(() =>
|
|
76
|
+
useLocalStorage<string>(key, "initialValue"),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const [storedValue] = result.current;
|
|
80
|
+
expect(storedValue).toBe("initialValue");
|
|
81
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
82
|
+
|
|
83
|
+
consoleErrorSpy.mockRestore();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useLocalStorage } from "./useLocalStorage";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
function useLocalStorage<T>(
|
|
4
|
+
key: string,
|
|
5
|
+
): [T | undefined, (value: T) => void, () => void];
|
|
6
|
+
function useLocalStorage<T>(
|
|
7
|
+
key: string,
|
|
8
|
+
initialValue: T,
|
|
9
|
+
): [T, (value: T) => void, () => void];
|
|
10
|
+
function useLocalStorage<T>(
|
|
11
|
+
key: string,
|
|
12
|
+
initialValue?: T,
|
|
13
|
+
): [T | undefined, (value: T) => void, () => void] {
|
|
14
|
+
const getStoredValue = (): T | undefined => {
|
|
15
|
+
try {
|
|
16
|
+
const item = localStorage.getItem(key);
|
|
17
|
+
if (item) {
|
|
18
|
+
return JSON.parse(item) as T;
|
|
19
|
+
}
|
|
20
|
+
return initialValue !== undefined ? initialValue : undefined;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error(`Error parsing localStorage key "${key}":`, error);
|
|
23
|
+
return initialValue !== undefined ? initialValue : undefined;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const [storedValue, setStoredValue] = useState<T | undefined>(getStoredValue);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
try {
|
|
31
|
+
if (storedValue !== undefined) {
|
|
32
|
+
localStorage.setItem(key, JSON.stringify(storedValue));
|
|
33
|
+
} else {
|
|
34
|
+
localStorage.removeItem(key);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
38
|
+
}
|
|
39
|
+
}, [key, storedValue]);
|
|
40
|
+
|
|
41
|
+
const setValue = (value: T) => {
|
|
42
|
+
setStoredValue(value);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const remove = () => {
|
|
46
|
+
try {
|
|
47
|
+
localStorage.removeItem(key);
|
|
48
|
+
setStoredValue(initialValue !== undefined ? initialValue : undefined);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Error removing localStorage key "${key}":`, error);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return [storedValue, setValue, remove];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default useLocalStorage;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react-vite";
|
|
2
|
+
import useMediaQuery from "../useMediaQuery";
|
|
3
|
+
|
|
4
|
+
const meta: Meta = {
|
|
5
|
+
title: "hooks/useMediaQuery",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default meta;
|
|
9
|
+
|
|
10
|
+
export const Default = () => {
|
|
11
|
+
const isSmallDevice = useMediaQuery("(max-width: 768px)");
|
|
12
|
+
const isMediumDevice = useMediaQuery(
|
|
13
|
+
"(min-width: 769px) and (max-width: 992px)",
|
|
14
|
+
);
|
|
15
|
+
const isLargeDevice = useMediaQuery(
|
|
16
|
+
"(min-width: 993px) and (max-width: 1200px)",
|
|
17
|
+
);
|
|
18
|
+
const isExtraLargeDevice = useMediaQuery("(min-width: 1201px)");
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex items-center justify-center h-screen">
|
|
22
|
+
<div
|
|
23
|
+
className={`
|
|
24
|
+
p-8 rounded-lg text-white text-center transition-all duration-500
|
|
25
|
+
${isSmallDevice ? "bg-blue-500" : ""}
|
|
26
|
+
${isMediumDevice ? "bg-green-500" : ""}
|
|
27
|
+
${isLargeDevice ? "bg-yellow-500" : ""}
|
|
28
|
+
${isExtraLargeDevice ? "bg-red-500" : ""}
|
|
29
|
+
`}
|
|
30
|
+
>
|
|
31
|
+
{isSmallDevice && "📱 You are using a Small Device (max-width: 768px)"}
|
|
32
|
+
{isMediumDevice && "💻 You are using a Medium Device (769px - 992px)"}
|
|
33
|
+
{isLargeDevice && "🖥️ You are using a Large Device (993px - 1200px)"}
|
|
34
|
+
{isExtraLargeDevice &&
|
|
35
|
+
"🖥️ You are using an Extra Large Device (min-width: 1201px)"}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useMediaQuery } from "./useMediaQuery";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const useMediaQuery = (query: string): boolean => {
|
|
4
|
+
const [matches, setMatches] = useState<boolean>(false);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const mediaQueryList = window.matchMedia(query);
|
|
8
|
+
const documentChangeHandler = () => setMatches(mediaQueryList.matches);
|
|
9
|
+
|
|
10
|
+
documentChangeHandler();
|
|
11
|
+
|
|
12
|
+
mediaQueryList.addEventListener("change", documentChangeHandler);
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
mediaQueryList.removeEventListener("change", documentChangeHandler);
|
|
16
|
+
};
|
|
17
|
+
}, [query]);
|
|
18
|
+
|
|
19
|
+
return matches;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default useMediaQuery;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useMemoizedFn } from "./useMemoizedFn";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isFunction } from "lodash";
|
|
2
|
+
import { useMemo, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
type noop = (this: any, ...args: any[]) => any;
|
|
5
|
+
|
|
6
|
+
type PickFunction<T extends noop> = (
|
|
7
|
+
this: ThisParameterType<T>,
|
|
8
|
+
...args: Parameters<T>
|
|
9
|
+
) => ReturnType<T>;
|
|
10
|
+
|
|
11
|
+
function useMemoizedFn<T extends noop>(fn: T) {
|
|
12
|
+
if (!isFunction(fn)) {
|
|
13
|
+
console.error(
|
|
14
|
+
`useMemoizedFn expected parameter is a function, got ${typeof fn}`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fnRef = useRef<T>(fn);
|
|
19
|
+
|
|
20
|
+
fnRef.current = useMemo<T>(() => fn, [fn]);
|
|
21
|
+
|
|
22
|
+
const memoizedFn = useRef<PickFunction<T>>(null);
|
|
23
|
+
if (!memoizedFn.current) {
|
|
24
|
+
memoizedFn.current = function (this, ...args) {
|
|
25
|
+
return fnRef.current.apply(this, args);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return memoizedFn.current as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default useMemoizedFn;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react-vite";
|
|
2
|
+
import { FormEvent } from "react";
|
|
3
|
+
import { Button, Input } from "../../../components";
|
|
4
|
+
import { getFormData } from "../../../utils";
|
|
5
|
+
import useAsync from "../../useAsync/useAsync";
|
|
6
|
+
import useMutation from "../useMutation";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* El hook `useMutation` permite manejar operaciones de mutación asincrónicas, proporcionando estados de carga, error y éxito.
|
|
10
|
+
*
|
|
11
|
+
* Opciones de configuración:
|
|
12
|
+
* - `fn`: La función de mutación que se ejecutará.
|
|
13
|
+
* - `onError`: Callback opcional que se llama cuando ocurre un error.
|
|
14
|
+
* - `onSuccess`: Callback opcional que se llama cuando la mutación es exitosa.
|
|
15
|
+
* - `onFinish`: Callback opcional que se llama cuando la mutación termina, sin importar si tuvo éxito o error. Es útil para hacer un `refetch` (de `useAsync`)
|
|
16
|
+
*
|
|
17
|
+
* Devuelve un objeto con las siguientes propiedades:
|
|
18
|
+
* - `mutate`: Función para ejecutar la mutación.
|
|
19
|
+
* - `loading`: Indica si la mutación está en progreso.
|
|
20
|
+
* - `error`: Contiene el error si la mutación falla.
|
|
21
|
+
* - `data`: Contiene los datos si la mutación es exitosa.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const meta: Meta = {
|
|
25
|
+
title: "hooks/useMutation",
|
|
26
|
+
parameters: {
|
|
27
|
+
docs: {
|
|
28
|
+
source: { type: "code" },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
|
|
35
|
+
interface FakeTodo {
|
|
36
|
+
userId: number;
|
|
37
|
+
id: number;
|
|
38
|
+
title: string;
|
|
39
|
+
completed: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fetchTodos = async (): Promise<FakeTodo> => {
|
|
43
|
+
const response = fetch("https://jsonplaceholder.typicode.com/todos/1").then(
|
|
44
|
+
(response) => response.json(),
|
|
45
|
+
);
|
|
46
|
+
if (!response) throw new Error("Todo not found");
|
|
47
|
+
return response;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const createTodo = async (title: string): Promise<FakeTodo> => {
|
|
51
|
+
if (!title) throw new Error("Title is required");
|
|
52
|
+
return await fetch("https://jsonplaceholder.typicode.com/posts", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
title: title,
|
|
56
|
+
body: "bar",
|
|
57
|
+
userId: 1,
|
|
58
|
+
}),
|
|
59
|
+
headers: {
|
|
60
|
+
"Content-type": "application/json; charset=UTF-8",
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
.then((response) => response.json())
|
|
64
|
+
.then((json) => json);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const Result = ({ loading = false, data = null, error = null }: any) => (
|
|
68
|
+
<pre className="rounded-md bg-slate-950 p-4 text-white mt-2">
|
|
69
|
+
{JSON.stringify(
|
|
70
|
+
{
|
|
71
|
+
loading,
|
|
72
|
+
error,
|
|
73
|
+
data,
|
|
74
|
+
},
|
|
75
|
+
undefined,
|
|
76
|
+
2,
|
|
77
|
+
)}
|
|
78
|
+
</pre>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const Form = {
|
|
82
|
+
render: () => {
|
|
83
|
+
const { refetch } = useAsync<FakeTodo>({
|
|
84
|
+
fn: fetchTodos,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { mutate, loading, data, error } = useMutation({
|
|
88
|
+
fn: createTodo,
|
|
89
|
+
onFinish: () => refetch(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
const data = getFormData(event);
|
|
95
|
+
if ("title" in data) mutate(data.title);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div>
|
|
100
|
+
<form className="space-y-2" onSubmit={handleSubmit}>
|
|
101
|
+
<Input placeholder="title" name="title" autoComplete="0" />
|
|
102
|
+
<Button type="submit" loading={loading}>
|
|
103
|
+
Enviar
|
|
104
|
+
</Button>
|
|
105
|
+
</form>
|
|
106
|
+
|
|
107
|
+
<Result loading={loading} data={data} error={error?.message} />
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import useMutation from "../useMutation";
|
|
4
|
+
|
|
5
|
+
const mockMutationSuccess = async (data: any) => {
|
|
6
|
+
return new Promise((resolve) => setTimeout(() => resolve(data), 100));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const mockMutationError = async () => {
|
|
10
|
+
return new Promise((_, reject) =>
|
|
11
|
+
setTimeout(() => reject(new Error("Mutation error")), 100),
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("useMutation hook", () => {
|
|
16
|
+
it("should initialize with default state", () => {
|
|
17
|
+
const { result } = renderHook(() =>
|
|
18
|
+
useMutation({ fn: () => Promise.resolve("test") }),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(result.current.loading).toBe(false);
|
|
22
|
+
expect(result.current.error).toBeNull();
|
|
23
|
+
expect(result.current.data).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should handle success", async () => {
|
|
27
|
+
const { result } = renderHook(() =>
|
|
28
|
+
useMutation({ fn: () => mockMutationSuccess("success") }),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
result.current.mutate();
|
|
32
|
+
await waitFor(() => expect(result.current.data).toBe("success"));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should handle error", async () => {
|
|
36
|
+
const { result } = renderHook(() =>
|
|
37
|
+
useMutation({ fn: () => mockMutationError() }),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await result.current.mutate();
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.log(e);
|
|
44
|
+
await waitFor(() => expect(result.current.error).not.toBeNull());
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should call onSuccess callback", async () => {
|
|
49
|
+
const onSuccess = vi.fn();
|
|
50
|
+
const { result } = renderHook(() =>
|
|
51
|
+
useMutation({ fn: () => mockMutationSuccess("success"), onSuccess }),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
result.current.mutate();
|
|
55
|
+
await waitFor(() => expect(onSuccess).toHaveBeenCalledWith("success"));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should call onError callback", async () => {
|
|
59
|
+
const onError = vi.fn();
|
|
60
|
+
const { result } = renderHook(() =>
|
|
61
|
+
useMutation({ fn: () => mockMutationError(), onError }),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await result.current.mutate();
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log(e);
|
|
68
|
+
await waitFor(() =>
|
|
69
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error)),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should call onFinish callback", async () => {
|
|
75
|
+
const onFinish = vi.fn();
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useMutation({ fn: () => mockMutationSuccess("success"), onFinish }),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
result.current.mutate();
|
|
81
|
+
await waitFor(() => expect(onFinish).toHaveBeenCalled());
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useMutation } from "./useMutation";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
type Mutation<TArgs extends any[], TReturn> = (
|
|
4
|
+
...args: TArgs
|
|
5
|
+
) => Promise<TReturn>;
|
|
6
|
+
|
|
7
|
+
interface UseMutationOptions<TArgs extends any[], TReturn> {
|
|
8
|
+
fn: Mutation<TArgs, TReturn>;
|
|
9
|
+
onError?: (error: Error) => void;
|
|
10
|
+
onSuccess?: (data: TReturn) => void;
|
|
11
|
+
onFinish?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface UseMutationReturn<TArgs extends any[], TReturn> {
|
|
15
|
+
mutate: Mutation<TArgs, TReturn>;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
data: TReturn | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const useMutation = <TArgs extends any[], TReturn>(
|
|
22
|
+
opts: UseMutationOptions<TArgs, TReturn>,
|
|
23
|
+
): UseMutationReturn<TArgs, TReturn> => {
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
const [error, setError] = useState<Error | null>(null);
|
|
26
|
+
const [data, setdata] = useState<TReturn | null>(null);
|
|
27
|
+
|
|
28
|
+
const mutate = useCallback(
|
|
29
|
+
async (...args: TArgs) => {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
setError(null);
|
|
32
|
+
setdata(null);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await opts.fn(...args);
|
|
36
|
+
opts.onSuccess?.(result);
|
|
37
|
+
setdata(result);
|
|
38
|
+
return result;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
const error = err as Error;
|
|
41
|
+
opts.onError?.(error);
|
|
42
|
+
setError(error);
|
|
43
|
+
throw error;
|
|
44
|
+
} finally {
|
|
45
|
+
opts.onFinish?.();
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[opts],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
mutate,
|
|
54
|
+
loading,
|
|
55
|
+
error,
|
|
56
|
+
data,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default useMutation;
|