I have the below component:
import * as React from "react";
import {useAppDispatch, useAppSelector} from "./store/hook";
import {fetchUser} from "./store/slices/user/auth-slice";
import {useDispatch} from "react-redux";
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchUser());
}, [])
const [loading, error] = useAppSelector((state: any) => [
state.auth.loading,
state.auth.error,
])
return (
<>
{loading ? (
<div
className="d-flex align-items-center justify-content-center"
style={{ height: "100vh" }}
>
<CircularProgress color="inherit" />
</div>
) : (
<Layout>
<Suspense fallback={<CircularProgress color="inherit" />}>
<Routes>
<Route path="/" element={<Navigate to="/regions/" replace />}/>
<Route path="ipam" element={<Ips />}/>
</Routes>
</Suspense>
</Layout>
)}
</>
);
}
export default App;
store/slices/user/auth-slice:
import {createAsyncThunk, createSlice} from "@reduxjs/toolkit";
import UserModel from "models/userAction";
import axios from "api/axios-base";
import {getUserDetails} from "../../../api/services";
import {useAppDispatch} from "../../hook";
export const fetchUser = createAsyncThunk('auth/user', async () => {
return getUserDetails().then((response) => {
return response.data;
}).catch(err => {
return err;
})
})
const initialState: UserModel = {
user_id: null,
email: '',
name: '',
isLoggedIn: false,
loading: false,
error: false,
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
Login(state: any, action: any) {
state = {
...initialState,
isLoggedIn: true
}
return state;
}
},
extraReducers: builder => {
builder
.addCase(fetchUser.pending, (state, action) => {
state = {
...initialState,
loading: true
}
return state;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state = {
...initialState,
...action.payload.data,
isLoggedIn: true,
loading: false
}
return state;
})
.addCase(fetchUser.rejected, (state) => {
state = {
...initialState,
loading: false
}
return state;
});
}
});
export const authActions = authSlice.actions;
export default authSlice;
Now the issue is Ips
which is a sub-component of App, renders twice:
function Ips() {
useEffect(() => {
alert('test')
}, [])
return (
<div className={classes.Ip}>
test
</div>
);
}
export default Ips;
hence running the alert("test") twice, I found out the issue comes after this portion of code within my authSlice turns loading True then making it false, when I comemnt it, there is no re-rendering happening to take place inside Child component.
builder
.addCase(fetchUser.pending, (state, action) => {
state = {
...initialState,
loading: true
}
return state;
})
How can I prevent such re-rendering?
CodePudding user response:
The problem is that you are re-rendering all app routes when calling that first fetchUser
API. The child component Ips
is not re-rendered, it is in fact remounted.
The simplest solution and IMO still correct (not a workaround): Initial value of loading
should be true
const initialState: UserModel = {
user_id: null,
email: '',
name: '',
isLoggedIn: false,
loading: true, // <--- `true` instead of `false`
error: false,
}
CodePudding user response:
Your usage of useAppSelector
here rerenders your application a lot more than it would need:
const [loading, error] = useAppSelector((state: any) => [
state.auth.loading,
state.auth.error,
])
This selector creates a new array every time it is called. useAppSelector
will execute your selector every time something is dispatched - and if the result is not equal (and here it is not referentially equal), it will rerender.
Call useAppSelector
twice instead to keep both results stable unless they are actually changing in the store (and require a rerender):
const loading = useAppSelector(state => state.auth.loading)
const error= useAppSelector(state => state.auth.error)
(if you have your useAppSelector
typed correctly, you don't need to specify a type for state
, it will be inferred automatically)
As for why your useEffect
fires twice: in React 18 with StrictMode on, during Developement every useEffect
will fire twice. That's how React works.