Search code examples
reactjsredux

How to prevent Redux dispatch from re-rendering the child components


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?


Solution

  • 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,
    }