0

I was using a free React template which was implemented with Context+Reducer and soon, after adding complexity, I ran accross the classic re-render caveat of useContext API. Is it possible to change my Context interface to one of Redux, since I don't want to re-render everything each time a single action is dispatched. I am currently using React v17.0.2


import { createContext, useContext, useReducer, useMemo } from "react";

// prop-types is a library for typechecking of props
import PropTypes from "prop-types";

//React main context
const MaterialUI = createContext();

// Setting custom name for the context which is visible on react dev tools
MaterialUI.displayName = "MaterialUIContext";

//React reducer
function reducer(state, action) {
  switch (action.type) {
    case "MINI_SIDENAV": {
      return { ...state, miniSidenav: action.value };
    }
    case "RESET_IDS": {
      return { ...state, chartIds: ["Main"] };
    }
    case "ADD_CHART": {
      return { ...state, chart: action.value, chartIds: [...state.chartIds, action.id] };
    }
    case "DELETE_CHART": {
      return {
        ...state,
        chart: action.value,
        chartIds: state.chartIds.filter((id) => id !== action.id),
      };
    }
    case "FILE_DATA": {
      return { ...state, fileData: action.value };
    }
    case "ANALYSIS_DATA": {
      return { ...state, analysisData: action.value };
    }
    case "DATA_UPLOADED": {
      return { ...state, isUploaded: action.value };
    }
    case "LAYOUT": {
      return { ...state, layout: action.value };
    }
    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
}

// React context provider
function MaterialUIControllerProvider({ children }) {
  const initialState = {
    miniSidenav: false,
    chart: {},
    chartIds: ["Main"],
    fileData: [],
    analysisData: [],
    isUploaded: false,
    layout: "page",
    darkMode: false,
  };

  const [controller, dispatch] = useReducer(reducer, initialState);

  const value = useMemo(() => [controller, dispatch], [controller, dispatch]);

  return <MaterialUI.Provider value={value}>{children}</MaterialUI.Provider>;
}

//React custom hook for using context
function useMaterialUIController() {
  const context = useContext(MaterialUI);

  if (!context) {
    throw new Error(
      "useMaterialUIController should be used inside the MaterialUIControllerProvider."
    );
  }

  return context;
}

// Typechecking props for the MaterialUIControllerProvider
MaterialUIControllerProvider.propTypes = {
  children: PropTypes.node.isRequired,
};

// Context module functions
const setMiniSidenav = (dispatch, value) => dispatch({ type: "MINI_SIDENAV", value });
const resetIds = (dispatch) => dispatch({ type: "RESET_IDS" });
const addNewChart = (dispatch, value, id) => dispatch({ type: "ADD_CHART", value, id });
const deleteChart = (dispatch, value, id) => dispatch({ type: "DELETE_CHART", value, id });
const setFileData = (dispatch, value) => dispatch({ type: "FILE_DATA", value });
const setAnalysisData = (dispatch, value) => dispatch({ type: "ANALYSIS_DATA", value });
const setIsUploaded = (dispatch, value) => dispatch({ type: "DATA_UPLOADED", value });
const setLayout = (dispatch, value) => dispatch({ type: "LAYOUT", value });

export {
  MaterialUIControllerProvider,
  useMaterialUIController,
  setMiniSidenav,
  resetIds,
  addNewChart,
  deleteChart,
  setFileData,
  setAnalysisData,
  setIsUploaded,
  setLayout,
};

I was trying to update fileData state and I ran across a breaking issue involving infinite re-renders, aswell as some non breaking issues regarding the rendering speed and processing! Any help is much appreciated

1 Answers1

0

its pretty straightforward. After you have installed redux and redux toolkit. You can refactor your state setup sth like this:

import { createSlice, configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';

// define initial state
const initialState = {
  miniSidenav: false,
  chart: {},
  chartIds: ["Main"],
  fileData: [],
  analysisData: [],
  isUploaded: false,
  layout: "page",
  darkMode: false,
};

// define the slice
const materialSlice = createSlice({
  name: 'material',
  initialState,
  reducers: {
    setMiniSidenav: (state, action) => {
      state.miniSidenav = action.payload;
    },
    resetIds: (state) => {
      state.chartIds = ["Main"];
    },
    addNewChart: (state, action) => {
      state.chart = action.payload.value;
      state.chartIds = [...state.chartIds, action.payload.id];
    },
    deleteChart: (state, action) => {
      state.chart = action.payload.value;
      state.chartIds = state.chartIds.filter((id) => id !== action.payload.id);
    },
    setFileData: (state, action) => {
      state.fileData = action.payload;
    },
    setAnalysisData: (state, action) => {
      state.analysisData = action.payload;
    },
    setIsUploaded: (state, action) => {
      state.isUploaded = action.payload;
    },
    setLayout: (state, action) => {
      state.layout = action.payload;
    },
  },
});

export const {
  setMiniSidenav,
  resetIds,
  addNewChart,
  deleteChart,
  setFileData,
  setAnalysisData,
  setIsUploaded,
  setLayout,
} = materialSlice.actions;

// configure store
const store = configureStore({
  reducer: materialSlice.reducer,
});

// context provider
function MaterialUIControllerProvider({ children }) {
  return <Provider store={store}>{children}</Provider>;
}

export { MaterialUIControllerProvider };

then you can use useDispatch and useSelector in your components to select a state and change them accordingly. As for infinite loop you might wanna check you are actually dispatching the action. Maybe it has something to do with dependencies inside your useEffect?

Super MaxLv4
  • 148
  • 9
  • Thank you very much for the refactoring aswell as the advice... The dependencies were fine, it was just stuck in an endless loop of re-rendering because any change in the state while using Context causes a re-render and everything was stuck in a loop. After changing Context to a Redux store, this issue has been cleared and I found many ways to improve my rendering performance now! Context really ISN'T appropriate for more complex apps with multiple prop drilling and calculations – Giorgos Boletos Jul 06 '23 at 11:51