@ahoo-wang/fetcher-react 3.1.9 → 3.2.1

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/README.md CHANGED
@@ -38,12 +38,13 @@ robust data fetching capabilities.
38
38
  - [useLatest Hook](#uselatest-hook)
39
39
  - [useRefs Hook](#userefs-hook)
40
40
  - [useKeyStorage Hook](#usekeystorage-hook)
41
+ - [useImmerKeyStorage Hook](#useimmerkeystorage-hook)
41
42
  - [Wow Query Hooks](#wow-query-hooks)
42
- - [useListQuery Hook](#uselistquery-hook)
43
- - [usePagedQuery Hook](#usepagedquery-hook)
44
- - [useSingleQuery Hook](#usesinglequery-hook)
45
- - [useCountQuery Hook](#usecountquery-hook)
46
- - [useListStreamQuery Hook](#useliststreamquery-hook)
43
+ - [useListQuery Hook](#uselistquery-hook)
44
+ - [usePagedQuery Hook](#usepagedquery-hook)
45
+ - [useSingleQuery Hook](#usesinglequery-hook)
46
+ - [useCountQuery Hook](#usecountquery-hook)
47
+ - [useListStreamQuery Hook](#useliststreamquery-hook)
47
48
  - [Best Practices](#best-practices)
48
49
  - [API Reference](#api-reference)
49
50
  - [License](#license)
@@ -551,6 +552,264 @@ const updateVolume = (newVolume: number) => {
551
552
  };
552
553
  ```
553
554
 
555
+ ### useImmerKeyStorage Hook
556
+
557
+ 🚀 **Immer-Powered Immutable State Management** - The `useImmerKeyStorage` hook extends `useKeyStorage` by integrating Immer's `produce` function, enabling intuitive "mutable" updates on stored values while maintaining immutability under the hood. Perfect for complex object manipulations with automatic storage synchronization.
558
+
559
+ #### Key Benefits
560
+
561
+ - **Intuitive Mutations**: Write code that looks mutable but produces immutable updates
562
+ - **Deep Object Support**: Effortlessly handle nested objects and arrays
563
+ - **Type Safety**: Full TypeScript support with compile-time error checking
564
+ - **Performance**: Optimized with Immer's structural sharing and minimal re-renders
565
+ - **Automatic Sync**: Changes automatically persist to storage and sync across components
566
+
567
+ #### When to Use
568
+
569
+ Choose `useImmerKeyStorage` over `useKeyStorage` when you need to:
570
+
571
+ - Update nested object properties
572
+ - Perform complex array operations (push, splice, etc.)
573
+ - Make multiple related changes atomically
574
+ - Work with deeply nested data structures
575
+
576
+ ```typescript jsx
577
+ import { KeyStorage } from '@ahoo-wang/fetcher-storage';
578
+ import { useImmerKeyStorage } from '@ahoo-wang/fetcher-react';
579
+
580
+ const MyComponent = () => {
581
+ const prefsStorage = new KeyStorage<{
582
+ theme: string;
583
+ volume: number;
584
+ notifications: boolean;
585
+ shortcuts: { [key: string]: string };
586
+ }>({
587
+ key: 'user-prefs'
588
+ });
589
+
590
+ // Without default value - can be null
591
+ const [prefs, updatePrefs, clearPrefs] = useImmerKeyStorage(prefsStorage);
592
+
593
+ return (
594
+ <div>
595
+ <p>Theme: {prefs?.theme || 'default'}</p>
596
+ <button onClick={() => updatePrefs(draft => { draft.theme = 'dark'; })}>
597
+ Switch to Dark Theme
598
+ </button>
599
+ <button onClick={() => updatePrefs(draft => { draft.volume += 10; })}>
600
+ Increase Volume
601
+ </button>
602
+ <button onClick={clearPrefs}>
603
+ Clear Preferences
604
+ </button>
605
+ </div>
606
+ );
607
+ };
608
+ ```
609
+
610
+ #### With Default Value
611
+
612
+ ```typescript jsx
613
+ const AudioControls = () => {
614
+ const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
615
+ key: 'audio-settings'
616
+ });
617
+
618
+ // With default value - guaranteed to be non-null
619
+ const [settings, updateSettings, resetSettings] = useImmerKeyStorage(
620
+ settingsStorage,
621
+ { volume: 50, muted: false }
622
+ );
623
+
624
+ return (
625
+ <div>
626
+ <p>Volume: {settings.volume}%</p>
627
+ <button onClick={() => updateSettings(draft => {
628
+ draft.volume = Math.min(100, draft.volume + 10);
629
+ draft.muted = false;
630
+ })}>
631
+ Increase Volume
632
+ </button>
633
+ <button onClick={() => updateSettings(draft => { draft.muted = !draft.muted; })}>
634
+ Toggle Mute
635
+ </button>
636
+ <button onClick={resetSettings}>
637
+ Reset to Default
638
+ </button>
639
+ </div>
640
+ );
641
+ };
642
+ ```
643
+
644
+ #### Advanced Usage Patterns
645
+
646
+ ##### Batch Updates
647
+
648
+ ```typescript jsx
649
+ const updateUserProfile = () => {
650
+ updatePrefs(draft => {
651
+ draft.theme = 'dark';
652
+ draft.notifications = true;
653
+ draft.volume = 75;
654
+ });
655
+ };
656
+ ```
657
+
658
+ ##### Array Operations
659
+
660
+ ```typescript jsx
661
+ const todoStorage = new KeyStorage<{
662
+ todos: Array<{ id: number; text: string; done: boolean }>;
663
+ }>({
664
+ key: 'todos',
665
+ });
666
+
667
+ const [state, updateState] = useImmerKeyStorage(todoStorage, { todos: [] });
668
+
669
+ // Add new todo
670
+ const addTodo = (text: string) => {
671
+ updateState(draft => {
672
+ draft.todos.push({
673
+ id: Date.now(),
674
+ text,
675
+ done: false,
676
+ });
677
+ });
678
+ };
679
+
680
+ // Toggle todo status
681
+ const toggleTodo = (id: number) => {
682
+ updateState(draft => {
683
+ const todo = draft.todos.find(t => t.id === id);
684
+ if (todo) {
685
+ todo.done = !todo.done;
686
+ }
687
+ });
688
+ };
689
+
690
+ // Remove completed todos
691
+ const clearCompleted = () => {
692
+ updateState(draft => {
693
+ draft.todos = draft.todos.filter(todo => !todo.done);
694
+ });
695
+ };
696
+ ```
697
+
698
+ ##### Nested Object Updates
699
+
700
+ ```typescript jsx
701
+ const configStorage = new KeyStorage<{
702
+ ui: { theme: string; language: string };
703
+ features: { [key: string]: boolean };
704
+ }>({
705
+ key: 'app-config',
706
+ });
707
+
708
+ const [config, updateConfig] = useImmerKeyStorage(configStorage, {
709
+ ui: { theme: 'light', language: 'en' },
710
+ features: {},
711
+ });
712
+
713
+ // Update nested properties
714
+ const updateTheme = (theme: string) => {
715
+ updateConfig(draft => {
716
+ draft.ui.theme = theme;
717
+ });
718
+ };
719
+
720
+ const toggleFeature = (feature: string) => {
721
+ updateConfig(draft => {
722
+ draft.features[feature] = !draft.features[feature];
723
+ });
724
+ };
725
+ ```
726
+
727
+ ##### Conditional Updates with Validation
728
+
729
+ ```typescript jsx
730
+ const updateVolume = (newVolume: number) => {
731
+ updateSettings(draft => {
732
+ if (newVolume >= 0 && newVolume <= 100) {
733
+ draft.volume = newVolume;
734
+ draft.muted = false; // Unmute when volume changes
735
+ }
736
+ });
737
+ };
738
+ ```
739
+
740
+ ##### Returning New Values
741
+
742
+ ```typescript jsx
743
+ // Replace entire state
744
+ const resetToFactorySettings = () => {
745
+ updateSettings(() => ({ volume: 50, muted: false }));
746
+ };
747
+
748
+ // Computed updates
749
+ const setMaxVolume = () => {
750
+ updateSettings(draft => ({ ...draft, volume: 100, muted: false }));
751
+ };
752
+ ```
753
+
754
+ ##### Error Handling
755
+
756
+ ```typescript jsx
757
+ const safeUpdate = (updater: (draft: any) => void) => {
758
+ try {
759
+ updatePrefs(updater);
760
+ } catch (error) {
761
+ console.error('Failed to update preferences:', error);
762
+ // Handle error appropriately
763
+ }
764
+ };
765
+ ```
766
+
767
+ #### Best Practices
768
+
769
+ ##### ✅ Do's
770
+
771
+ - Use for complex object updates and array manipulations
772
+ - Leverage Immer's draft mutations for readable code
773
+ - Combine multiple related changes in a single update call
774
+ - Use default values for guaranteed non-null state
775
+ - Handle errors appropriately in update functions
776
+
777
+ ##### ❌ Don'ts
778
+
779
+ - Don't modify the draft parameter directly with assignment (`draft = newValue`)
780
+ - Don't perform side effects inside updater functions
781
+ - Don't rely on reference equality for object comparisons
782
+ - Don't use for simple primitive value updates (use `useKeyStorage` instead)
783
+
784
+ ##### Performance Tips
785
+
786
+ - Batch related updates together to minimize storage operations
787
+ - Use functional updates when the new state depends on the previous state
788
+ - Consider using `useCallback` for updater functions if they're recreated frequently
789
+ - Profile your updates if working with very large objects
790
+
791
+ ##### TypeScript Integration
792
+
793
+ ```typescript jsx
794
+ // Define strict types for better safety
795
+ type UserPreferences = {
796
+ theme: 'light' | 'dark' | 'auto';
797
+ volume: number; // 0-100
798
+ notifications: boolean;
799
+ shortcuts: Record<string, string>;
800
+ };
801
+
802
+ const prefsStorage = new KeyStorage<UserPreferences>({
803
+ key: 'user-prefs',
804
+ });
805
+
806
+ // TypeScript will catch invalid updates
807
+ const [prefs, updatePrefs] = useImmerKeyStorage(prefsStorage);
808
+
809
+ // This will cause a TypeScript error:
810
+ // updatePrefs(draft => { draft.theme = 'invalid'; });
811
+ ```
812
+
554
813
  ## Wow Query Hooks
555
814
 
556
815
  The Wow Query Hooks provide advanced data querying capabilities with built-in state management for conditions,
@@ -1743,6 +2002,73 @@ const [theme, setTheme] = useKeyStorage(themeStorage, 'light');
1743
2002
  // theme: string (never null)
1744
2003
  ```
1745
2004
 
2005
+ ### useImmerKeyStorage
2006
+
2007
+ ```typescript
2008
+ // Without default value - can return null
2009
+ function useImmerKeyStorage<T>(
2010
+ keyStorage: KeyStorage<T>,
2011
+ ): [
2012
+ T | null,
2013
+ (updater: (draft: T | null) => T | null | void) => void,
2014
+ () => void,
2015
+ ];
2016
+
2017
+ // With default value - guaranteed non-null
2018
+ function useImmerKeyStorage<T>(
2019
+ keyStorage: KeyStorage<T>,
2020
+ defaultValue: T,
2021
+ ): [T, (updater: (draft: T) => T | null | void) => void, () => void];
2022
+ ```
2023
+
2024
+ A React hook that provides Immer-powered immutable state management for a KeyStorage instance. Extends `useKeyStorage` by integrating Immer's `produce` function, allowing intuitive "mutable" updates on stored values while maintaining immutability.
2025
+
2026
+ **Type Parameters:**
2027
+
2028
+ - `T`: The type of value stored in the key storage
2029
+
2030
+ **Parameters:**
2031
+
2032
+ - `keyStorage`: The KeyStorage instance to subscribe to and manage. Should be a stable reference (useRef, memo, or module-level instance)
2033
+ - `defaultValue` _(optional)_: The default value to use when storage is empty. When provided, the hook guarantees the returned value will never be null
2034
+
2035
+ **Returns:**
2036
+
2037
+ A tuple containing:
2038
+
2039
+ - **Current value**: `T | null` (without default) or `T` (with default)
2040
+ - **Update function**: `(updater: (draft: T | null) => T | null | void) => void` - Immer-powered updater function
2041
+ - **Clear function**: `() => void` - Function to remove the stored value
2042
+
2043
+ **Updater Function:**
2044
+
2045
+ The updater function receives a `draft` parameter that can be mutated directly. Immer will produce an immutable update from these mutations. The updater can also return a new value directly or `null` to clear the storage.
2046
+
2047
+ **Examples:**
2048
+
2049
+ ```typescript
2050
+ // Basic object updates
2051
+ const [user, updateUser] = useImmerKeyStorage(userStorage);
2052
+ updateUser(draft => {
2053
+ if (draft) {
2054
+ draft.name = 'John';
2055
+ draft.age = 30;
2056
+ }
2057
+ });
2058
+
2059
+ // Array operations
2060
+ const [todos, updateTodos] = useImmerKeyStorage(todosStorage, []);
2061
+ updateTodos(draft => {
2062
+ draft.push({ id: 1, text: 'New todo', done: false });
2063
+ });
2064
+
2065
+ // Returning new values
2066
+ updateTodos(() => [{ id: 1, text: 'Reset todos', done: false }]);
2067
+
2068
+ // Clearing storage
2069
+ updateTodos(() => null);
2070
+ ```
2071
+
1746
2072
  ### useListQuery
1747
2073
 
1748
2074
  ```typescript