Skip to content

Commit 5bcad16

Browse files
committed
fix: Add deps to useCallback hooks
Also: - Adds tests - Adds Angus Houston as an author (thanks!) Fixes #14
1 parent f152968 commit 5bcad16

3 files changed

Lines changed: 65 additions & 4 deletions

File tree

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
Nathan Buchar <hello@nathanbuchar.com> (www.nathanbuchar.com)
22
Asa Ayers <asa@asaayers.com> (www.asaayers.com)
3+
Angus Houston <angus.houston@outlook.com.au>

src/thunk-reducer.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,23 @@ function useThunkReducer(reducer, initialArg, init = (a) => a) {
2727

2828
// State management.
2929
const state = useRef(hookState);
30-
const getState = useCallback(() => state.current, []);
30+
const getState = useCallback(() => state.current, [state]);
3131
const setState = useCallback((newState) => {
3232
state.current = newState;
3333
setHookState(newState);
34-
}, []);
34+
}, [state, setHookState]);
3535

3636
// Reducer.
3737
const reduce = useCallback((action) => {
3838
return reducer(getState(), action);
39-
}, []);
39+
}, [reducer, getState]);
4040

4141
// Augmented dispatcher.
4242
const dispatch = useCallback((action) => {
4343
return typeof action === 'function'
4444
? action(dispatch, getState)
4545
: setState(reduce(action));
46-
}, []);
46+
}, [getState, setState, reduce]);
4747

4848
return [hookState, dispatch];
4949
}

test/thunk-reducer.spec.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,64 @@ describe('thunk reducer hook tests', () => {
143143
expect(dispatch(incrementAndReturnCount())).toEqual(1);
144144
});
145145
});
146+
147+
test('hook result does not change if its inputs are changed', () => {
148+
const renderHookResult = renderHook(() => useThunkReducer(reducer, { count: 0 }));
149+
150+
// Capture the state and dispatch after first render
151+
const [state, dispatch] = renderHookResult.result.current;
152+
153+
// Ensure that the hook state is updated, then rerender the hook
154+
renderHookResult
155+
.waitForNextUpdate()
156+
.then(() => {
157+
renderHookResult.rerender();
158+
})
159+
.then(() => {
160+
// Capture the new state and dispatch returned after the hook is
161+
// rerendered. This should not change if the hook props are not changed
162+
const [newState, newDispatch] = renderHookResult.result.current;
163+
164+
expect(newState).toBe(state);
165+
expect(newDispatch).toBe(dispatch);
166+
});
167+
});
168+
169+
test('hook result changes if inputs change', () => {
170+
function newReducer(state, { type }) {
171+
switch (type) {
172+
case 'decrement':
173+
return { count: state.count - 1 };
174+
default:
175+
throw new Error();
176+
}
177+
}
178+
179+
const renderHookResult = renderHook(({ reducerProp }) => {
180+
return useThunkReducer(reducerProp, { count: 0 });
181+
}, {
182+
initialProps: {
183+
reducerProp: reducer,
184+
},
185+
});
186+
187+
// Capture the state and dispatch after first render
188+
const [state, dispatch] = renderHookResult.result.current;
189+
190+
// Ensure that the hook state is updated, then rerender the hook
191+
renderHookResult
192+
.waitForNextUpdate()
193+
.then(() => {
194+
renderHookResult.rerender({ reducerProp: newReducer });
195+
})
196+
.then(() => {
197+
// Capture the new state and dispatch returned after the hook is
198+
// rerendered. Because the hook reducer is changed, the returned
199+
// dispatch function must also be changed
200+
const [newState, newDispatch] = renderHookResult.result.current;
201+
202+
expect(newState).toBe(state);
203+
expect(newDispatch).not.toBe(dispatch);
204+
});
205+
});
146206
});

0 commit comments

Comments
 (0)