Skip to content

Commit cbfad09

Browse files
committed
add spec, fix impl
1 parent 3971f79 commit cbfad09

File tree

2 files changed

+76
-10
lines changed

2 files changed

+76
-10
lines changed

__tests__/03_stale_props.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import { createStore } from 'redux';
3+
4+
import { render, cleanup, act } from '@testing-library/react';
5+
6+
import { Provider, useSelector } from '../src/index';
7+
8+
describe('stale props spec', () => {
9+
afterEach(cleanup);
10+
11+
it('ignores transient errors in selector (e.g. due to stale props)', () => {
12+
const Parent = () => {
13+
const count = useSelector((state) => state.count);
14+
return <Child parentCount={count} />;
15+
};
16+
17+
const Child = ({ parentCount }) => {
18+
const selector = (state) => {
19+
if (state.count !== parentCount) {
20+
throw new Error();
21+
}
22+
return state.count + parentCount;
23+
};
24+
const result = useSelector(selector);
25+
return <div>{result}</div>;
26+
};
27+
28+
const store = createStore((state = { count: -1 }) => ({ count: state.count + 1 }));
29+
30+
const App = () => (
31+
<Provider store={store}>
32+
<Parent />
33+
</Provider>
34+
);
35+
36+
render(<App />);
37+
act(() => {
38+
expect(() => store.dispatch({ type: '' })).not.toThrowError();
39+
});
40+
});
41+
42+
it('ensures consistency of state and props in selector', () => {
43+
let selectorSawInconsistencies = false;
44+
45+
const Parent = () => {
46+
const count = useSelector((state) => state.count);
47+
return <Child parentCount={count} />;
48+
};
49+
50+
const Child = ({ parentCount }) => {
51+
const selector = (state) => {
52+
selectorSawInconsistencies = selectorSawInconsistencies || (state.count !== parentCount);
53+
return state.count + parentCount;
54+
};
55+
const result = useSelector(selector);
56+
return <div>{result}</div>;
57+
};
58+
59+
const store = createStore((state = { count: -1 }) => ({ count: state.count + 1 }));
60+
61+
const App = () => (
62+
<Provider store={store}>
63+
<Parent />
64+
</Provider>
65+
);
66+
67+
render(<App />);
68+
act(() => {
69+
store.dispatch({ type: '' });
70+
});
71+
expect(selectorSawInconsistencies).toBe(false);
72+
});
73+
});

src/useSelector.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,14 @@ export const useSelector = (selector, eqlFn, opts) => {
1111
customContext = defaultContext,
1212
} = opts || (!isFunction(eqlFn) && eqlFn) || {};
1313
const { state, subscribe } = useContext(customContext);
14-
const [selected, updateSelected] = useReducer((prevSelected, nextState) => {
15-
const nextSelected = selector(nextState);
14+
const [selected, updateSelected] = useReducer((prevSelected) => {
15+
const nextSelected = selector(state);
1616
if (equalityFn(prevSelected, nextSelected)) return prevSelected;
1717
return nextSelected;
1818
}, state, selector);
19-
let selectedToReturn = selected;
20-
const currSelected = selector(state);
21-
if (!equalityFn(selected, currSelected)) {
22-
// schedule another update, because state from context has been changed
23-
updateSelected(state);
24-
selectedToReturn = currSelected;
25-
}
2619
useEffect(() => {
2720
const unsubscribe = subscribe(updateSelected);
2821
return unsubscribe;
2922
}, [subscribe]);
30-
return selectedToReturn;
23+
return selected;
3124
};

0 commit comments

Comments
 (0)