Reputation: 101
I have experienced useEffect function from React hook. When I use it with useSelector from React-redux, it worked so weird.
const initialState = { a: 0 };
function mainReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case 'A': return { ...state, a: payload }
default:
return state;
}
}
function MyComponent(props) {
const { a } = useSelector(state => state.mainReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log('did mount: ', a);
dispatch({ type: 'A', payload: 1 })
}, []);
useEffect(() => {
console.log('use effect: ', a);
dispatch({ type: 'A', payload: a });
}, [a])
return (<View style={styles.container}>
<Text style={styles.text}>{a}</Text>
</View>);
};
Log:
did mount ran: 0
useEffect ran: 0
The last result is variable 'a' = 0
As I understand,
After the first render, both effect ran sequentially in their order in the code.
(Step 1) So the first effect run fist -> log 'did mount ran: 0'. Then it dispatch value 1 to store
(Step 2) The second effect run after -> log 'did mount ran: 0'. Then it dispatch value 0 to store
But what I don't understand is the second effect must track the change from variable 'a', so there will be:
In the following render time:
(Step 3) the second useEffect should be run when the value 'a' change from 0 to 1 (from Step 1).
And then:
(Step 4) it should have the third re-render when the value change again from 1 to 0 (from Step 2)
So the log should be:
did mount ran: 0
useEffect ran: 0
useEffect ran: 1
useEffect ran: 0
Could you please explain to me what I'm missing? Thank you
Upvotes: 10
Views: 22998
Reputation: 6919
Your both dispatch are called after first render so even before your second render value is 0 so your second useEffect won't be able detect change as there is no change.
Let's see what is happening in your render method
First Render:
a = 0
first useEffect: dispatch({ a : 1 })
second useEffect: dispatch({ a : 0 })
so now in your redux store a is 0.
Second Render
a = 0
first useEffect: doesn't run as there is no dependency
second useEffect: doesn't run as a hasn't changed.
Upvotes: 5
Reputation: 8428
useSelector(state => state.mainReducer);
it doesn't make any sense
there should be a simple state transformation (subselection)
const a = useSelector(state => state.a)
taken directly from redux docs:
const counter = useSelector(state => state.counter)
you can see effect (from store change) with slightly changed component
function MyComponent(props) {
const a = useSelector(state => state.a);
const dispatch = useDispatch();
console.log('render: ', a);
useEffect(() => {
console.log('use effect: ', a);
dispatch({ type: 'A', payload: a });
}, [a])
useEffect(() => {
console.log('did mount: ', a);
dispatch({ type: 'A', payload: 1 })
}, []);
return (<View style={styles.container}>
<Text style={styles.text}>{a}</Text>
</View>);
};
It should result in log:
render: 0
// initial state use effect: 0
// first effect run 0
... processed in store by reducer but results in the same state ...a
readed from state on the beginning of render did mount: 0
// 'old' a
1
... changed state in redux store .... rendered text 0
...
...
// useSelector
forces rerendering - change detected
render: 1
// latest dispatched value, processed by reducers into new state, rereaded by selector use effect: 1
// useEffect
works AS EXPECTED as an effect of a
change 1
...
...
Of course dispatch from other component will force update in this component ... if value will be different.
Upvotes: 1