I've read through documents about hooks but don't see enough detailed information about the lifecycle of components when working with hooks, hence I need your help.
To be more specific, I have a sample code with the following related questions: https://github.com/khoitnm/practice-react-redux/tree/main/pro-00-old-react-version/src/comp-04-clickButton
Question 1) My functional component uses 3 hooks: useSelector, useDispatch, useEffect, but then why the component is executed 4 times (detail in the code and screenshot below)?
From my understanding, it should be only 3 times:
- Initiation
- The
useSelectortriggers this execution - The
useEffecttriggers this execution. TheuseDispatchwill not trigger any additional execution, then why the data show 4 times?
Below are my code and screenshot. The component:
let componentCount = 0;
let returnCount = 0;
let useSelectorCount = 0;
let useEffectCount = 0;
const Comp04ClickButtonPage = (): JSX.Element => {
componentCount++;
console.log(`[${componentCount}] component - START ----------------------`);
// useSelector
const stringValueState = useSelector((rootState: RootState): string => {
useSelectorCount++;
console.log(`[${componentCount}] selectorCount: ${useSelectorCount}`);
const result = rootState.comp04ClickButtonSlice.stringValue;
return result;
});
console.log(`[${componentCount}] component - after useSelectorCount`);
// useEffect
const dispatch = useDispatch();
useEffect(() => {
useEffectCount++;
console.log(`[${componentCount}] effectCount: ${useEffectCount}.`);
dispatch(thunkComp04ClickButton(`${new Date().getTime()}`));
}, [dispatch]);
console.log(`[${componentCount}] component - after useEffect`);
const onClickButton = () => {
componentCount = returnCount = useSelectorCount = useEffectCount = 0; // reset counts
console.log(`[${componentCount}] onClickNewValue`);
dispatch(thunkComp04ClickButton(`Random ${new Date().getTime()}`));
};
// render
returnCount++;
return (
<div>
{console.log(`[${componentCount}] renderCount: ${returnCount}`)}
<div>
[{componentCount}], useEffect: {useEffectCount}, useSelectorCount: {useSelectorCount}, returnCount: {returnCount}
</div>
<button onClick={onClickButton}>Click Button</button>
</div>
);
}
The thunk:
export const thunkComp04ClickButton = (stringValue: string): AppThunk => async (dispatch) => {
try {
console.log(`AppThunk: saveStringValue "${stringValue}" start dispatch`);
dispatch(setComp04ClickButtonState({ stringValue: stringValue }));
console.log(`AppThunk: saveStringValue "${stringValue}" end dispatch`);
} catch (err) {
console.error(`AppThunk: saveStringValue error dispatch` + err, err);
}
};
The slice:
type Comp04ClickButtonState = {
stringValue: string;
};
const initialState: Comp04ClickButtonState = {
stringValue: 'Init Comp04ClickButtonState',
};
const comp04ClickButtonSlice = createSlice({
name: 'comp04ClickButtonSlice',
initialState,
reducers: {
setComp04ClickButtonState(state, action: PayloadAction<Comp04ClickButtonState>) {
console.log(`Slice: setComp04ClickButtonState "${action.payload.stringValue}"`);
return action.payload;
},
},
});
export const { setComp04ClickButtonState } = comp04ClickButtonSlice.actions;
export default comp04ClickButtonSlice.reducer;
In the screenshot 1: you'll see that the componentCount is [4] and returnCount: 4
Question 2) When the component is triggered, why the useSelector is executed multiple times?
In more detail, we can see in the console log: In the second execution of the component, the selectorCount increase from 2 to 5 times (totally 4 times)
[2] selectorCount: 2
[2] component - after useSelectorCount
[2] component - after useEffect
[2] renderCount: 2
[2] selectorCount: 3
[2] effectCount: 1.
AppThunk: saveStringValue "1610983429863" start dispatch
Slice: setComp04ClickButtonState "1610983429863"
[2] selectorCount: 4
AppThunk: saveStringValue "1610983429863" end dispatch
[2] selectorCount: 5
In summary, when I open that web page, the final result is:
- useEffect: 1 (as expected)
- componentCount = returnCount: 4 (many times)
- useSelector: 7 (why so many times???)
UPDATED: As @lawrence-witt mentioned, the <React.StrictMode> causes double-invokes on various things, so I change my index.tsx to use <React.Fragment> like this:
const render = () => {
ReactDOM.render(
<React.Fragment>
<Provider store={store}>
<App />
</Provider>
</React.Fragment>,
document.getElementById('root')
);
};
As the result, the component now is executed only 2 times. However, as you see in the screenshot 2, the console log still shows that the useSelector is triggered 4 times when the component is executed the first time. Hence the question 2 still remains: Why is it triggered so many times?
[1] component - START ----------------------
[1] selectorCount: 1
[1] component - after useSelectorCount
[1] component - after useEffect
[1] renderCount: 1
[1] selectorCount: 2
[1] effectCount: 1.
AppThunk: saveStringValue "1611023432655" start dispatch
Slice: setComp04ClickButtonState "1611023432655"
[1] selectorCount: 3
AppThunk: saveStringValue "1611023432655" end dispatch
[1] selectorCount: 4