redux-simplify-hooks 1.0.0 → 1.0.2

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/LICENSE CHANGED
@@ -1,17 +1,17 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- Copyright 2025 Mbulelo Phillip Peyi
6
-
7
- Licensed under the Apache License, Version 2.0 (the "License");
8
- you may not use this file except in compliance with the License.
9
- You may obtain a copy of the License at
10
-
11
- http://www.apache.org/licenses/LICENSE-2.0
12
-
13
- Unless required by applicable law or agreed to in writing, software
14
- distributed under the License is distributed on an "AS IS" BASIS,
15
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- See the License for the specific language governing permissions and
17
- limitations under the License.
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2025 Mbulelo Phillip Peyi
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
package/README.md CHANGED
@@ -1,147 +1,143 @@
1
- Excellent — let’s write a **professional `README.md`** for your package `redux-simplify-hooks`:
2
-
3
- ---
4
-
5
- ````markdown
6
- # redux-simplify-hooks
7
-
8
- > Redux made simple: modern, type-safe React hooks for deeply nested state, actions, and selectors — fully compatible with Redux Toolkit and React 16.8+.
9
-
10
- [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
11
-
12
- ---
13
-
14
- ## ✨ What is redux-simplify-hooks?
15
-
16
- **redux-simplify-hooks** is a lightweight collection of hooks that simplifies working with Redux state in React.
17
- It provides ergonomic and modern alternatives to `useSelector` and `useDispatch`, allowing you to:
18
-
19
- - Select deeply nested state using dot notation.
20
- - Read and write Redux state like React’s `useState()`.
21
- - Bind Redux actions easily with hooks.
22
- - Select multiple state slices at once.
23
-
24
- ✅ Fully type-safe
25
- ✅ Compatible with **React 16.8+** (Hooks onward)
26
- ✅ Compatible with **Redux Toolkit**
27
- Supports React 17, 18, 19+
28
-
29
- ---
30
-
31
- ## 📦 Installation
32
-
33
- First install the required peer dependencies if not already installed:
34
-
35
- ```bash
36
- npm install react react-dom react-redux @reduxjs/toolkit
37
- ````
38
-
39
- Then install redux-simplify-hooks:
40
-
41
- ```bash
42
- npm install redux-simplify-hooks
43
- ```
44
-
45
- ---
46
-
47
- ## Peer Dependencies
48
-
49
- | Package | Version |
50
- | ---------------- | ---------------------- |
51
- | react | >= 16.8 |
52
- | react-dom | >= 16.8 |
53
- | react-redux | ^7.1.0, ^8.0.0, ^9.0.0 |
54
- | @reduxjs/toolkit | >= 1.0.0 |
55
-
56
- ---
57
-
58
- ## 🚀 Usage
59
-
60
- ### 1️⃣ `useRedux()`
61
-
62
- Read nested state using dot-path notation:
63
-
64
- ```typescript
65
- import { useRedux } from 'redux-simplify-hooks';
66
-
67
- const MyComponent = () => {
68
- const { state, dispatch } = useRedux('user.profile.name');
69
-
70
- return <div>Hello, {state}</div>;
71
- };
72
- ```
73
-
74
- ### 2️⃣ `useReduxMulti()`
75
-
76
- Select multiple state paths at once:
77
-
78
- ```typescript
79
- import { useReduxMulti } from 'redux-simplify-hooks';
80
-
81
- const { state } = useReduxMulti(['user.profile.name', 'counter.value']);
82
-
83
- console.log(state['user.profile.name']);
84
- console.log(state['counter.value']);
85
- ```
86
-
87
- ### 3️⃣ `useReduxState()`
88
-
89
- Read & write Redux state like React's `useState`:
90
-
91
- ```typescript
92
- import { useReduxState } from 'redux-simplify-hooks';
93
- import { counterSlice } from './counterSlice';
94
-
95
- const [count, setCount] = useReduxState('counter.value', counterSlice.actions.setCounter);
96
-
97
- setCount(42);
98
- ```
99
-
100
- ### 4️⃣ `useBoundAction()`
101
-
102
- Bind Redux actions to dispatch automatically:
103
-
104
- ```typescript
105
- import { useBoundAction } from 'redux-simplify-hooks';
106
- import { counterSlice } from './counterSlice';
107
-
108
- const incrementByAmount = useBoundAction(counterSlice.actions.incrementByAmount);
109
-
110
- incrementByAmount(5); // dispatches action
111
- ```
112
-
113
- ---
114
-
115
- ## 🧪 Testing
116
-
117
- redux-simplify-hooks is fully tested using:
118
-
119
- * Jest
120
- * @testing-library/react
121
-
122
- ```bash
123
- npm run test
124
- ```
125
-
126
- ---
127
-
128
- ## 📄 License
129
-
130
- This project is licensed under the **Apache License 2.0**.
131
-
132
- © 2025 Mbulelo Phillip Peyi
133
-
134
- ---
135
-
136
- ## 💡 Motivation
137
-
138
- Redux can be powerful but verbose — **redux-simplify-hooks** aims to make Redux feel as simple as React's `useState()` without sacrificing its robustness.
139
-
140
- ---
141
-
142
- ## 💬 Contributions
143
-
144
- PRs and issues are welcome! If you want to contribute, please fork and submit a pull request.
145
-
146
- ---
147
-
1
+ ````markdown
2
+ # redux-simplify-hooks
3
+
4
+ > Redux made simple: modern, type-safe React hooks for deeply nested state, actions, and selectors — fully compatible with Redux Toolkit and React 16.8+.
5
+
6
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## What is redux-simplify-hooks?
11
+
12
+ **redux-simplify-hooks** is a lightweight collection of hooks that simplifies working with Redux state in React.
13
+ It provides ergonomic and modern alternatives to `useSelector` and `useDispatch`, allowing you to:
14
+
15
+ - Select deeply nested state using dot notation.
16
+ - Read and write Redux state like React’s `useState()`.
17
+ - Bind Redux actions easily with hooks.
18
+ - Select multiple state slices at once.
19
+
20
+ Fully type-safe
21
+ Compatible with **React 16.8+** (Hooks onward)
22
+ Compatible with **Redux Toolkit**
23
+ Supports React 17, 18, 19+
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ First install the required peer dependencies if not already installed:
30
+
31
+ ```bash
32
+ npm install react react-dom react-redux @reduxjs/toolkit
33
+ ````
34
+
35
+ Then install redux-simplify-hooks:
36
+
37
+ ```bash
38
+ npm install redux-simplify-hooks
39
+ ```
40
+
41
+ ---
42
+
43
+ ## ⚙ Peer Dependencies
44
+
45
+ | Package | Version |
46
+ | ---------------- | ---------------------- |
47
+ | react | >= 16.8 |
48
+ | react-dom | >= 16.8 |
49
+ | react-redux | ^7.1.0, ^8.0.0, ^9.0.0 |
50
+ | @reduxjs/toolkit | >= 1.0.0 |
51
+
52
+ ---
53
+
54
+ ## Usage
55
+
56
+ ### 1 `useRedux()`
57
+
58
+ Read nested state using dot-path notation:
59
+
60
+ ```typescript
61
+ import { useRedux } from 'redux-simplify-hooks';
62
+
63
+ const MyComponent = () => {
64
+ const { state, dispatch } = useRedux('user.profile.name');
65
+
66
+ return <div>Hello, {state}</div>;
67
+ };
68
+ ```
69
+
70
+ ### 2 `useReduxMulti()`
71
+
72
+ Select multiple state paths at once:
73
+
74
+ ```typescript
75
+ import { useReduxMulti } from 'redux-simplify-hooks';
76
+
77
+ const { state } = useReduxMulti(['user.profile.name', 'counter.value']);
78
+
79
+ console.log(state['user.profile.name']);
80
+ console.log(state['counter.value']);
81
+ ```
82
+
83
+ ### 3 `useReduxState()`
84
+
85
+ Read & write Redux state like React's `useState`:
86
+
87
+ ```typescript
88
+ import { useReduxState } from 'redux-simplify-hooks';
89
+ import { counterSlice } from './counterSlice';
90
+
91
+ const [count, setCount] = useReduxState('counter.value', counterSlice.actions.setCounter);
92
+
93
+ setCount(42);
94
+ ```
95
+
96
+ ### 4 `useBoundAction()`
97
+
98
+ Bind Redux actions to dispatch automatically:
99
+
100
+ ```typescript
101
+ import { useBoundAction } from 'redux-simplify-hooks';
102
+ import { counterSlice } from './counterSlice';
103
+
104
+ const incrementByAmount = useBoundAction(counterSlice.actions.incrementByAmount);
105
+
106
+ incrementByAmount(5); // dispatches action
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Testing
112
+
113
+ redux-simplify-hooks is fully tested using:
114
+
115
+ * Jest
116
+ * @testing-library/react
117
+
118
+ ```bash
119
+ npm run test
120
+ ```
121
+
122
+ ---
123
+
124
+ ## License
125
+
126
+ This project is licensed under the **Apache License 2.0**.
127
+
128
+ © 2025 Mbulelo Phillip Peyi
129
+
130
+ ---
131
+
132
+ ## Motivation
133
+
134
+ Redux can be powerful but verbose — **redux-simplify-hooks** aims to make Redux feel as simple as React's `useState()` without sacrificing its robustness.
135
+
136
+ ---
137
+
138
+ ## Contributions
139
+
140
+ PRs and issues are welcome! If you want to contribute, please fork and submit a pull request.
141
+
142
+ ---
143
+
@@ -1,3 +1,2 @@
1
- import { AnyAction } from 'redux';
2
- export declare function useBoundAction<Args extends any[]>(actionCreator: (...args: Args) => AnyAction): (...args: Args) => void;
1
+ export declare function useBoundAction<Args extends any[]>(actionCreator: (...args: Args) => any): (...args: Args) => any;
3
2
  //# sourceMappingURL=useBoundAction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useBoundAction.d.ts","sourceRoot":"","sources":["../../../src/hooks/useBoundAction.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,wBAAgB,cAAc,CAAC,IAAI,SAAS,GAAG,EAAE,EAAE,aAAa,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,SAAS,aAG/D,IAAI,UAGlC"}
1
+ {"version":3,"file":"useBoundAction.d.ts","sourceRoot":"","sources":["../../../src/hooks/useBoundAction.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,CAAC,IAAI,SAAS,GAAG,EAAE,EAC/C,aAAa,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,GAAG,aAIR,IAAI,SAGlC"}
@@ -1,8 +1,8 @@
1
- import { useDispatch } from 'react-redux';
2
- import { useCallback } from 'react';
1
+ import { useCallback } from "react";
2
+ import { useDispatch } from "react-redux";
3
3
  export function useBoundAction(actionCreator) {
4
4
  const dispatch = useDispatch();
5
5
  return useCallback((...args) => {
6
- dispatch(actionCreator(...args));
6
+ return dispatch(actionCreator(...args));
7
7
  }, [dispatch, actionCreator]);
8
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useRedux.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRedux.ts"],"names":[],"mappings":"AAIA,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;WAgBnC,CAAC;;EACnC"}
1
+ {"version":3,"file":"useRedux.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRedux.ts"],"names":[],"mappings":"AAIA,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;WAe3C,CAAC;;EAC3B"}
@@ -1,18 +1,19 @@
1
- import { useSelector, useDispatch } from 'react-redux';
2
- import { useMemo } from 'react';
1
+ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
2
+ import { useMemo, useRef } from 'react';
3
3
  import { getNestedValue } from '../utils/getNestedValue';
4
4
  export function useRedux(statePath, defaultValue) {
5
5
  const dispatch = useDispatch();
6
+ // Store defaultValue in a ref so changes to object literals don't break memoization
7
+ const defaultRef = useRef(defaultValue);
8
+ defaultRef.current = defaultValue;
6
9
  const selector = useMemo(() => {
7
10
  return (state) => {
8
- if (!statePath) {
9
- // If no path specified, don't return full state — return defaultValue instead
10
- return defaultValue;
11
- }
11
+ if (!statePath)
12
+ return defaultRef.current;
12
13
  const val = getNestedValue(state, statePath);
13
- return val !== undefined ? val : defaultValue;
14
+ return val !== undefined ? val : defaultRef.current;
14
15
  };
15
- }, [statePath, defaultValue]);
16
- const selectedState = useSelector(selector);
17
- return { state: selectedState, dispatch };
16
+ }, [statePath]); // Removed defaultValue from deps to keep selector stable
17
+ const state = useSelector(selector, shallowEqual);
18
+ return { state: state, dispatch };
18
19
  }
@@ -1,5 +1,5 @@
1
1
  export declare function useReduxMulti(paths?: string[]): {
2
- state: any;
2
+ state: Record<string, any>;
3
3
  dispatch: import("redux").Dispatch<import("redux").UnknownAction>;
4
4
  };
5
5
  //# sourceMappingURL=useReduxMulti.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useReduxMulti.d.ts","sourceRoot":"","sources":["../../../src/hooks/useReduxMulti.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAM,EAAO;;;EAWjD"}
1
+ {"version":3,"file":"useReduxMulti.d.ts","sourceRoot":"","sources":["../../../src/hooks/useReduxMulti.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAM,EAAO;;;EAmBjD"}
@@ -1,10 +1,22 @@
1
- import { useRedux } from './useRedux';
1
+ import { useSelector } from "react-redux";
2
+ import { useDispatch } from "react-redux";
3
+ import { getNestedValue } from "../utils/getNestedValue";
4
+ import { shallowEqual } from "react-redux";
2
5
  export function useReduxMulti(paths = []) {
3
- const dispatch = useRedux().dispatch;
4
- const states = paths.map(path => {
5
- const { state } = useRedux(path);
6
- return [path, state];
6
+ const dispatch = useDispatch();
7
+ // Memoize the path string to keep the selector identity stable
8
+ const pathsKey = paths.join(',');
9
+ const state = useSelector((rootState) => {
10
+ return paths.reduce((acc, path) => {
11
+ acc[path] = getNestedValue(rootState, path);
12
+ return acc;
13
+ }, {});
14
+ }, (prev, next) => {
15
+ // Faster than JSON.stringify, safer than default ref check
16
+ const keys = Object.keys(next);
17
+ if (keys.length !== Object.keys(prev).length)
18
+ return false;
19
+ return keys.every(key => shallowEqual(prev[key], next[key]));
7
20
  });
8
- const state = Object.fromEntries(states);
9
21
  return { state, dispatch };
10
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useReduxState.d.ts","sourceRoot":"","sources":["../../../src/hooks/useReduxState.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,EAChC,YAAY,CAAC,EAAE,CAAC,GACf,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAY5C"}
1
+ {"version":3,"file":"useReduxState.d.ts","sourceRoot":"","sources":["../../../src/hooks/useReduxState.ts"],"names":[],"mappings":"AAGA,wBAAgB,aAAa,CAAC,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,EAChC,YAAY,CAAC,EAAE,CAAC,GACf,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAe5C"}
@@ -1,11 +1,15 @@
1
- import { useRedux } from './useRedux';
1
+ import { useCallback, useRef } from "react";
2
+ import { useRedux } from "./useRedux";
2
3
  export function useReduxState(statePath, actionCreator, defaultValue) {
3
4
  const { state, dispatch } = useRedux(statePath, defaultValue);
4
- const setState = (newValue) => {
5
+ // Keep track of the latest state in a ref to avoid stale closures in useCallback
6
+ const stateRef = useRef(state);
7
+ stateRef.current = state;
8
+ const setState = useCallback((newValue) => {
5
9
  const valueToDispatch = typeof newValue === 'function'
6
- ? newValue(state)
10
+ ? newValue(stateRef.current)
7
11
  : newValue;
8
12
  dispatch(actionCreator(valueToDispatch));
9
- };
13
+ }, [dispatch, actionCreator]); // state is removed from deps; setState is now stable!
10
14
  return [state, setState];
11
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"getNestedValue.d.ts","sourceRoot":"","sources":["../../../src/utils/getNestedValue.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAc1D"}
1
+ {"version":3,"file":"getNestedValue.d.ts","sourceRoot":"","sources":["../../../src/utils/getNestedValue.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAQ1D"}
@@ -1,14 +1,11 @@
1
1
  export function getNestedValue(obj, path) {
2
2
  if (!path)
3
3
  return obj;
4
- return path.split('.').reduce((acc, key) => {
4
+ const keys = path.split(/[.[\]]+/).filter(Boolean);
5
+ return keys.reduce((acc, key) => {
5
6
  if (acc === undefined || acc === null)
6
7
  return undefined;
7
- const arrayMatch = key.match(/^(\w+)\[(\d+)\]$/);
8
- if (arrayMatch) {
9
- const [, arrKey, index] = arrayMatch;
10
- return acc[arrKey]?.[Number(index)];
11
- }
12
- return acc?.[key];
8
+ const isIndex = /^\d+$/.test(key);
9
+ return isIndex ? acc[Number(key)] : acc[key];
13
10
  }, obj);
14
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redux-simplify-hooks",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Redux made simple: modern, type-safe React hooks for deeply nested state, actions, and selectors — fully compatible with Redux Toolkit and React 16.8+.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",