33 <p >A <a href =" https://react.dev/ " >React</a > hook for working with <a href =" https://redux-toolkit.js.org/ " >Redux Toolkit</a > slices, with zero setup and boilerplate ⚛️ 🛠️
44 </p >
55 <p >
6- <a href="https://www.npmjs.com/package/use-rtk-slice"><img alt="npm" src="https://img.shields.io/npm/v/use-rtk-slice.svg"></a>
7- <a href="https://www.npmjs.com/package/use-rtk-slice" target="_blank"><img alt="Downloads per month" src="https://img.shields.io/npm/dm/use-rtk-slice.svg" /></a>
8- <a href="https://github.com/Lambdaphile/use-rtk-slice/blob/main/src/useSlice.test.tsx"><img alt="Coverage" src="https://img.shields.io/badge/coverage -100%25-brightgreen"></a>
9- <a href="https://www.typescriptlang.org/"><img alt="TypeScript Ready" src="https://img.shields.io/badge/TypeScript-Ready-blue.svg"></a>
6+ <a href="https://www.npmjs.com/package/use-rtk-slice" target="_blank" ><img alt="npm" src="https://img.shields.io/npm/v/use-rtk-slice.svg?label=NPM "></a>
7+ <a href="https://www.npmjs.com/package/use-rtk-slice" target="_blank"><img alt="Downloads per month" src="https://img.shields.io/npm/dm/use-rtk-slice.svg?label=Downloads " /></a>
8+ <a href="https://github.com/Lambdaphile/use-rtk-slice/blob/main/src/useSlice.test.tsx" target="_blank" ><img alt="Coverage" src="https://img.shields.io/badge/Coverage -100%25-brightgreen"></a>
9+ <a href="https://www.typescriptlang.org/" target="_blank" ><img alt="TypeScript Ready" src="https://img.shields.io/badge/TypeScript-Ready-blue.svg"></a>
1010 </p >
1111 <pre >npm i <a href =" https://www.npmjs.com/package/use-rtk-slice " >use-rtk-slice</a ></pre >
1212</div >
1313<hr />
1414
15- Using Redux Toolkit slices with plain ` useSelector ` and ` useDispatch ` hooks requires:
15+ Using Redux Toolkit slices with plain ` useSelector ` and ` useDispatch ` hooks often requires:
1616
17- 1 . Manually defining typed versions of ` useSelector ` and ` useDispatch ` in TypeScript projects - [ Define ` useAppSelector ` and ` useAppDispatch ` ] ( https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks ) .
17+ 1 . Manually defining typed versions of ` useSelector ` and ` useDispatch ` in TypeScript projects: [ Define ` useAppSelector ` and ` useAppDispatch ` ] ( https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks ) .
18182 . Repeatedly writing ` const dispatch = useDispatch() ` just to dispatch an action.
19193 . Slice selectors are not bound - using them requires passing the relevant slice state, e.g., ` selector({ stateName: state }) ` .
2020
2121The ` useSlice ` hook from ` use-rtk-slice ` handles all of this: it binds actions and selectors internally, and returns fully typed, ready-to-use slice state, actions, and selectors.
2222
23+ ## Contents
24+
25+ - [ Usage] ( #usage )
26+ - [ Testing (Mocking Slices)] ( #testing-mocking-slices )
27+ - [ Mock State] ( #mock-state )
28+ - [ Mock Selectors] ( #mock-selectors )
29+ - [ Mock and Spy on Actions] ( #mock-and-spy-on-actions )
30+
2331## Usage
2432
2533Define a RTK slice:
2634
2735` todosSlice.ts `
2836
2937``` ts
30- import { createSlice , PayloadAction } from ' @reduxjs/toolkit'
38+ import { createSlice , type PayloadAction } from ' @reduxjs/toolkit'
3139
3240interface Todo {
3341 id: number
@@ -38,12 +46,12 @@ interface Todo {
3846const initialState: Todo [] = [
3947 {
4048 id: Date .now (),
41- name: ' Feed the cat 🐱 ' ,
49+ name: ' 🧪 Write unit tests ' ,
4250 done: false
4351 }
4452]
4553
46- export const todosSlice = createSlice ({
54+ const todosSlice = createSlice ({
4755 name: ' todos' ,
4856 initialState: initialState ,
4957 reducers: {
@@ -54,19 +62,24 @@ export const todosSlice = createSlice({
5462 return state .map ((todo ) =>
5563 todo .id === action .payload ? { ... todo , done: ! todo .done } : todo
5664 )
65+ },
66+ removeTodo(state , action : PayloadAction <Todo [' id' ]>) {
67+ return state .filter ((todo ) => todo .id !== action .payload )
5768 }
5869 },
5970 selectors: {
60- selectCompleted : (state ) => state .filter ((todo ) => todo .done )
71+ selectCompleted(state ) {
72+ return state .filter ((todo ) => todo .done )
73+ }
6174 }
6275})
6376```
6477
65- Destructure the ` state ` , and the bound ` actions ` and ` selectors ` from the slice as needed using, the ` useSlice ` hook:
78+ Destructure the ` state ` , and the bound ` actions ` and ` selectors ` from the slice as needed, using the ` useSlice ` hook:
6679
6780` TodoList.tsx `
6881
69- ``` tsx
82+ ``` ts
7083import useSlice from ' use-rtk-slice'
7184import { todosSlice } from ' ./todosSlice'
7285
@@ -84,15 +97,93 @@ function TodoList() {
8497 checked = {done }
8598 onChange = {() => actions.toggleTodo(id)}
8699 / >
87- { name }
100+ {done ? < s >{ name }</ s > : name }
88101 < / label >
102+ < button onClick = {() => actions.removeTodo(id)}> 🗑️< / button >
89103 < / li >
90104 ))}
91105 < / ul >
92- <p >
93- <strong >Completed Todos:</strong > { selectors .selectCompleted ().length }
94- </p >
106+ <p >Completed todo count : {selectors .selectCompleted ().length}</p >
95107 < / div >
96108 )
97109}
98110```
111+
112+ ## Testing (Mocking Slices)
113+
114+ To mock slices, use the ` mockSlice ` utility from ` use-rtk-slice/test/vitest ` or ` use-rtk-slice/test/jest ` :
115+
116+ ### Mock State
117+
118+ ``` ts
119+ import { mockSlice } from ' use-rtk-slice/test/vitest'
120+ // or
121+ import { mockSlice } from ' use-rtk-slice/test/jest'
122+
123+ import { App } from ' ./App'
124+ import { todoSlice } from ' ./todoSlice'
125+
126+ describe (' TodoList' , () => {
127+ beforeEach (() => {
128+ mockSlice .beforeEach ()
129+ })
130+
131+ it (' should render todos' , () => {
132+ mockSlice (todosSlice , {
133+ state: [
134+ { id: 0 , name: ' 🧪 Write unit tests' , done: false },
135+ { id: 1 , name: ' 📝 Update README' , done: false }
136+ ]
137+ })
138+
139+ render (<App />)
140+
141+ const todos = screen .getAllByRole (' listitem' )
142+ expect (todos ).toHaveLength (2 )
143+ })
144+ })
145+ ```
146+
147+ ### Mock Selectors
148+
149+ ``` ts
150+ describe (' TodoList' , () => {
151+ beforeEach (() => {
152+ mockSlice .beforeEach ()
153+ })
154+
155+ it (' should render completed todo count' , () => {
156+ mockSlice (todosSlice , {
157+ selectCompleted : () => [{ id: 0 , name: ' 🧪 Write unit tests' , done: true }]
158+ })
159+
160+ render (<App />)
161+
162+ expect (screen .getByText (' Completed todo count: 1' )).toBeInTheDocument ()
163+ })
164+ })
165+ ```
166+
167+ ### Mock and Spy on Actions
168+
169+ ``` ts
170+ describe (' TodoList' , () => {
171+ beforeEach (() => {
172+ mockSlice .beforeEach ()
173+ })
174+
175+ it (' should toggle todos' , () => {
176+ const { toggleTodo } = mockSlice (todosSlice , {
177+ state: [{ id: 0 , name: ' 🧪 Write unit tests' , done: false }]
178+ })
179+
180+ render (<App />)
181+
182+ const todoToggle = screen .getByRole (' checkbox' )
183+ fireEvent .click (todoToggle )
184+ expect (toggleTodo ).toHaveBeenCalled ()
185+ })
186+ })
187+ ```
188+
189+ ** Note:** Calling ` beforeEach(() => { mockSlice.beforeEach() }) ` is required to ensure test cases run in isolation.
0 commit comments