Migrate React Context to Redux

173 Views Asked by At

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

There are 1 best solutions below

1
Super MaxLv4 On BEST ANSWER

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?