Home > database >  when I use useDispatch inside useEffect my component keep rendering
when I use useDispatch inside useEffect my component keep rendering

Time:01-23

When I use dispatch as follows in my react component, My component keeps rendering. How can I avoid that?

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(reportsActionCreators.changeSalesDashboardData(someData));
  }, []);

in the parent component, I'm using useSelector as this. But didn't use this report's data.

  const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
  return {
    selectedSalesTab: state.reports.selectedSalesTab,
  };

this is the parent component I'm using.

const SalesReports: FC = () => {
  const dispatch = useDispatch();
  const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
    return {
      selectedSalesTab: state.reports.selectedSalesTab,
    };
  });

  const getPageContent = useMemo(() => {
    switch (selectedSalesTab) {
      case salesReportsTabs[0].id:
        return <Dashboard />;
      default:
        return <div>not found :(</div>;
    }
  }, [selectedSalesTab]);

  return (
    <div className="sales-report-wrapper">
      <GTTabs
        id="sales-reports-tabs"
        onClickTab={(tab: Tab) => dispatch(reportsActionCreators.changeSalesTab(tab.id))}
        tabs={salesReportsTabs}
        defaultSelectedTabId={selectedSalesTab}
      />
      <div>{getPageContent}</div>
    </div>
  );
};

export default SalesReports;

this is the Child component I'm using

const Dashboard: FC = () => {
  const repostsRxjs = rxjsConfig(reportingAxios);
  const dispatch = useDispatch();

  useEffect(() => {
    repostsRxjs
      .post<SalesDashboardItem[]>(
        '/sales-data/order-details/6087bc3606ff073930a10848?timezone=Asia/Dubai&from=2022-09-03T00:00:00.00Z&to=2022-12-25T00:00:00.00Z&size=10',
        {
          brandIds: [],
          channelIds: [],
          kitchenIds: [],
          countryIds: [],
        },
      )
      .pipe(
        take(1),
        catchError((err: any) => of(console.log(err))),
      )
      .subscribe((response: SalesDashboardItem[] | void) => {
        if (response) {
          dispatch(reportsActionCreators.changeSalesDashboardData(response));
        }
      });
  }, []);

  const { isActiveFilter } = useSelector<RootState, any>((state: RootState) => {
    return {
      isActiveFilter: state.filterData.isActiveFilter,
    };
  });

  return (
    <>
      <div
        onClick={() => {
          dispatch(filterssActionCreators.handleFilterPanel(!isActiveFilter));
          dispatch(
            filterssActionCreators.changeSelectedFiltersType([
              FilterTypes.BRAND,
              FilterTypes.CHANNEL,
              FilterTypes.COUNTRY,
              FilterTypes.KITCHEN,
            ]),
          );
        }}
      >
        Dashboard
      </div>
      {isActiveFilter && <FilterPanel />}
    </>
  );
};

export default Dashboard;

Actions

  import { SalesDashboardItem } from 'app/models/Reports';
import { actionCreator } from 'app/state/common';

export type ChangeSalesTabPayload = string;
export type ChangeSalesDashboardDataPayload = SalesDashboardItem[];

export const reportsActionTypes = {
  CHANGE_SALES_TAB: 'CHANGE_SALES_TAB',
  CHANGE_SALES_DASHABOARD_DATA: 'CHANGE_SALES_DASHABOARD_DATA',
};

export const reportsActionCreators = {
  changeSalesTab: actionCreator<ChangeSalesTabPayload>(reportsActionTypes.CHANGE_SALES_TAB),
  changeSalesDashboardData: actionCreator<ChangeSalesDashboardDataPayload>(
    reportsActionTypes.CHANGE_SALES_DASHABOARD_DATA,
  ),
};

export type ReportsAction = {
  type: typeof reportsActionTypes.CHANGE_SALES_TAB | typeof reportsActionTypes.CHANGE_SALES_DASHABOARD_DATA;
  payload: ChangeSalesTabPayload | ChangeSalesDashboardDataPayload;
};

Reducer

    import { SalesDashboardItem } from 'app/models/Reports';
import { salesReportsTabs } from 'app/utils/reports';
import { reportsActionTypes, ReportsAction } from './actions';

export type ReportsState = {
  selectedSalesTab: string;
  salesDashboardFilterData: {
    brands: string[];
    kitchens: string[];
    channels: string[];
    countries: string[];
  };
  salesDashBoardDatta: SalesDashboardItem[];
};

const initialState: ReportsState = {
  selectedSalesTab: salesReportsTabs[0].id,
  salesDashboardFilterData: {
    brands: [],
    kitchens: [],
    channels: [],
    countries: [],
  },
  salesDashBoardDatta: [],
};

export default (state = initialState, action: ReportsAction): ReportsState => {
  switch (action.type) {
    case reportsActionTypes.CHANGE_SALES_TAB:
      return { ...state, selectedSalesTab: action.payload as string };
    case reportsActionTypes.CHANGE_SALES_DASHABOARD_DATA:
      return { ...state, salesDashBoardDatta: action.payload as SalesDashboardItem[] };
    default:
      return state;
  }
};

root reducer

import { combineReducers } from 'redux';
import SidePanelReducer from './reducers/sidepanel.reducer';
import authReducer from './auth';
import onboardingReducer from './onboarding';
import applicationReducer from './application';
import inventoryConfigReducer from './inventoryConfig/inventory.reducer';
import reportsReducer from './reports';
import filtersReducer from './filter';

const rootReducer = combineReducers({
  sidePanel: SidePanelReducer,
  auth: authReducer,
  onboarding: onboardingReducer,
  application: applicationReducer,
  inventory: inventoryConfigReducer,
  reports: reportsReducer,
  filterData: filtersReducer,
});

export default rootReducer;

when I'm adding the dispatch action in useEffect(componentDidMount) this looping is happening. Otherwise, this code works fine. How can I avoid that component rerendering?

CodePudding user response:

I think the issue is that the useSelector hook is returning a new object reference each time which triggers the useMemo hook to re-memoize an "instance" of the Dashboard component. The new "instance" of Dashboard then mounts and runs its useEffect hook which dispatches an action that updates the state.reports state in the Redux store.

Instead of creating and returning a new object reference to destructure selectedSalesTab from, just return the state.reports object directly.

Change

const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
  return {
    selectedSalesTab: state.reports.selectedSalesTab,
  };
});

to

const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
  return state.reports;
});
  • Related