Looking to address excessive re-renders caused by Redux on a single page and seeking assistance in identifying and resolving the problem

Currently, I am in the process of developing the dashboard website for a music theory application company. This platform will allow users to manage various aspects such as their personal information, courses, assignments, and media content.

The dashboard site is being constructed using React hooks and Redux Toolkit. Specifically, the page I am currently focusing on is designed for teacher users to edit assignment sets, which includes assignment details and associated exercises. To ensure easy navigation, we are implementing a URL structure like

course/{courseID}/assignment/{assignmentID}
. This means that each time this page is accessed, API calls need to be made to retrieve necessary backend data.

My Redux store object is relatively straightforward. Below is an example relevant to the data on the page in question:

store: {
  singleCourse: {
    courseInfo: <courseInfoObj>,
    students: [...<studentsObjs>],
    assignments: [...<assignmentObjs>],
  },
  singleAssignment: {
    assignmentInfo: <assignmentInfoObj>,
    exercises: [...<exerciseObjs>],
  },
  contentLibrary: {
    library: [...<libraryObjs>]
  }
}

The structure of the page involves a single useEffect utilizing batch to group necessary dispatch actions. Data from the Redux store is accessed using useSelector. On this particular page, I need to execute 6 different actions (communicating with 6 API routes) to collect all required data, which is stored across the 3 reducers mentioned above. To obtain the data from Redux, I am using

const {singleCourse, singleAssignment, contentLibrary} = useSelector(state => state)
.

After observing up to 40+ re-renders on the page (especially with a higher number of exercises), I have identified that the excessive re-renders may be linked to the useSelector calls. I am aware of memoized selectors for reducing re-renders, but integrating them into my scenario has proven challenging.

Furthermore, I am dispatching 6 actions on this page that update 3 separate reducers. Is this practice considered unfavorable?

While the pages load swiftly and are responsive, the concern of potential issues stemming from numerous re-renders persists.

Thank you.

Edit: additional code for reference.

AssignmentDetailPage.tsx

const AssignmentDetailPage = () => {
  const { courseID, assignmentID } = useParams();

// dispatches

  useEffect(() => {
    batch(() => {
      
      dispatch(getCourseDetail(courseID));
      dispatch(getCourseStudents(courseID));
      dispatch(getCourseAssignments(courseID));
      dispatch(getAssignmentAndDocuments(assignmentID));
      dispatch(listAssignmentDueDateExtensions(assignmentID));
      dispatch(getEntireContentLibrary());
    })
  }, [dispatch]);



  // Grabbing from Redux store.



  const selectedCourse = useHarmoniaSelector(state=> state.selectedCourse);
  const selectedAssignment = useHarmoniaSelector(state => state.selectedAssignment);
  const contentLibrary = useHarmoniaSelector(state => state.contentLibrary);


  // Retrieving assignment info from assignments state.

  const assignment = selectedAssignment.assignment as Assignment || {};

  const dueDateExtensions = selectedAssignment.dueDateExtensions as DueDateExtension[];


  const documents = selectedAssignment.documents || [] as Document[];


  // Retrieving array of assignments and determining the assignmentIndex of the current assignment.
  const courseAssignments = selectedCourse.assignments as Assignment[] || [];

  const courseStudents = selectedCourse.students || [] as CourseStudent[];

}

Here's a snippet related to Redux operations with the selectedAssignment:

selectedAssignment.ts

type SelectedAssignmentInitialState = {
  assignment?: Assignment | null,
  documents?: Document[] | null,
  dueDateExtensions?: DueDateExtension[] | null
}
const initialState: SelectedAssignmentInitialState = {};

// actions
export const getAssignmentAndDocuments = createAsyncThunk
<Assignment, number, {rejectValue: void}>
('assignments/getAssignmentDetail', async(assignmentID, {dispatch, rejectWithValue}) => {
  try{
    const response = await API.get(`/api/assignments/${assignmentID}`);
    return response.data.data as Assignment;
  }catch(e){
    dispatch(pushErrorNotification(errorMessage(e.data)));
    return rejectWithValue();
  }
});

export const listAssignmentDueDateExtensions = createAsyncThunk<DueDateExtension[], number, {rejectValue: void}  >('assignment/listAssignmentDueDateExtensions', async (assignmentID, {dispatch, rejectWithValue}) => {
  try{
    const response = await API.get(`/api/assignments/${assignmentID}/due-day-extensions`);
    return response.data.data as DueDateExtension[];
  }catch(e){
    dispatch(pushErrorNotification(e.data));
    return rejectWithValue();
  }
});

// reducer

const selectedAssignmentSlice = createSlice({
  name: 'selectedAssignment',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
    .addCase(
      getAssignmentAndDocuments.fulfilled,
      ((state, {payload}) => {
        // assignmentDetail is everything but documents; documents kept on separate key in state obj to keep things simpler.
        const {course, documents, due_at, id, released_at, set_key, show_after_due, title, weight} = payload;
        state.assignment = {course, due_at, id, released_at, set_key, show_after_due, title, weight};
        state.documents = documents;
      })
      )
.addCase(listAssignmentDueDateExtensions.fulfilled, ((state, {payload}) => {
        state.dueDateExtensions = payload;
      }))


Answer №1

Profiling the application is essential to identify the real bottlenecks. It's important not to focus solely on writing perfectly cached code or memoizing everything prematurely, as this can impact developer productivity, maintainability, and introduce bugs.

However, it is still crucial to strive for performant code.

Check out this resource on what to include in state, and avoid including "derived data" such as calculations: https://redux.js.org/usage/deriving-data-selectors

Here's a useful guide on how useSelector functions: https://react-redux.js.org/api/hooks#performance

Highlighted key points to consider:

useSelector runs after every action, triggering a re-render if the result differs in reference comparison from the previous one.

When calling useSelector, the state object is returned, triggering a re-render on each action due to reference equality changes.

Create selectors that return each item in the state tree to avoid unnecessary re-renders unless the specific part of the state has changed.

Utilize Immer/Redux toolkit to preserve unchanged portions of the state tree. Aim to return the old state unless the reducer acts on that specific state.

Identify the root cause of re-renders after implementing basic useSelector optimization (avoid returning the entire state tree) and explore appropriate solutions.

Is it considered bad practice to dispatch 6 different actions on this page that update 3 separate reducers?

Dispatching 6 actions to update 3 reducers is not excessive. A single async action in Redux toolkit triggers 3 actions.

The page loads quickly and remains responsive, but I fear that excessive re-renders may lead to significant problems.

Monitor the situation closely to detect any issues, but refrain from premature optimization. Focus on avoiding common mistakes (e.g., relying on useSelector default reference checks) without over-optimizing. Save optimization efforts for proven bottlenecks.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The JSONP request failed with an error stating: ReferenceError: document is not defined

My internship project involves developing a mobile application based on the website www.claroline.net using Nativescript and Angular 2. I have successfully implemented the login function, allowing users to sign in to the app. Next, I need to integrate not ...

Is it possible for input properties of array type to change on multiple components in Angular 9?

Encountering an interesting issue that I need clarification on. Recently, I developed a compact Angular application to demonstrate the problem at hand. The puzzling situation arises when passing an Array (any[] or Object[]) as an @Input property to a chil ...

Why doesn't Typescript 5.0.3 throw an error for incompatible generic parameters in these types?

------------- Prompt and background (Not crucial for understanding the question)---------------- In my TypeScript application, I work with objects that have references to other objects. These references can be either filled or empty, as illustrated below: ...

Exploring TypeScript: Understanding how to define Object types with variable properties names

Having an issue with a React + TypeScript challenge that isn't causing my app to crash, but I still want to resolve this lingering doubt! It's more of a query than a problem! My goal is to set the property names of an object dynamically using va ...

Enhancing Error Handling in Node.js with TypeScript Typing

Currently, I am working with NodeJs/express and Typescript. When making a request, the compiler automatically understands the type of (req, res), requiring no additional action on my part. UserRouter.post('/user/me/avatar', avatar, async (req, ...

Encountered an issue with the Dynamic Form: TypeError - The property 'value' is undefined and cannot be read

RESOLVED An incorrect value was causing an issue with the onChange function. public onChange = (e:any, key:any) => { this.setState({ [key]: e.target.value }); }; I'm struggling to resolve an error while inputting data into my form in T ...

Styling with CSS in Angular 2+ can be quite challenging

Hey there, I'm new to Angular 4 and running into some troubles with styling my application. I tried adding a background image to the body, which worked fine, and then added a component to display content, also looking good. Now, when I added a second ...

Using TypeScript to define data types for Supabase payloads

Currently, I'm working on integrating supabase into my ReactJS Typescript project. However, I'm unsure about the data type of the channel payload response and I aim to extract the eventType along with the new data. const handleInserts = () => ...

When working on styling a different Styled Component, how should one define the type of props required?

I'm currently working on a NextJS project using styled components and typescript. I have customized a div element like this: export const ClippedOverlay = styled( ( props: React.DetailedHTMLProps< React.HTMLAttributes<HTMLDivElement& ...

What is the reason behind using `Partial<>` to enclose the Vue props?

I am currently working with a combination of technologies, and I am struggling to pinpoint the root cause of the issue. Here is the Tech Stack: Vue 3 TypeScript Vite VSCode / Volar unplugin-vue-macros (https://github.com/sxzz/vue-macros) unplugin-vue-com ...

Unable to pass response from httpclient post method to another custom function in Angular 4

I've implemented the addUser(newUser) function in my sign-in.service.ts file like this: addUser(newUser) { const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; let body = JS ...

Troubleshooting notifications encountered while detaching hooks from a class component - Custom Navigation Bar based on User Roles

To create a header using Material UI, Redux, and React with a dropdown menu that displays only sections accessible to the user, I have modified my code. However, I am encountering errors different from what I initially had when using class component hooks. ...

What are the distinctions between using findById({_id:historyId}) and findById(historyId) in Mongoose?

While working on one of my projects, I encountered a situation that left me a bit confused. I am trying to understand if both approaches outlined below will yield the same output, and if so, why? async getHistory( historyId: string) { const { h ...

Ensure the inferred type is asserted in TypeScript

Is there a more elegant approach to assert the type TypeScript inferred for a specific variable? Currently, I am using the following method: function assertType<T>(value: T) { /* no op */ } assertType<SomeType>(someValue); This technique prov ...

Angular: Enable function to await Observable completion before returning result

I require assistance with the user function below: getUser(uuid: string): Observable<WowUserDataModel> { let user: WowUserDataModel = { login: null, userUuid: uuid, firstName: null, lastName: null, displayName: nul ...

What is the best way to make the SPA load with the tab displaying the highest value?

I have a React single-page application using Typescript and Material UI. One challenge I'm facing is creating a tab menu with the current month and all previous months, where the last month should be active when the page loads. Despite researching on ...

Error: Unable to iterate through posts due to a TypeError in next.js

Hi there, this is my first time asking for help here. I'm working on a simple app using Next.js and ran into an issue while following a tutorial: Unhandled Runtime Error TypeError: posts.map is not a function Source pages\posts\index.tsx (1 ...

Suggestions for fixing the error message "Cannot read property 'map' of undefined" in React/Redux

Encountering the all too familiar error message "Cannot read property 'map' of undefined" once again. Initially, my list of items (movies) loads perfectly within a <ul><li></li></ul>. However, when I trigger my action by c ...

Utilizing the String Enum for mapping an interface with an index

Using Typescript, I aim to make use of my string enumeration: export const enum MutationKeys { registerUser = 'registration/REGISTER', registerUserCompleted = 'registration/REGISTER_COMPLETED' } This allows the string values t ...

Mongoose does not compare BCRYPT passwords that are empty

I'm currently working on incorporating bcrypt into my mongoose model using typescript. Referencing this link as a guide. However, since my project is in typescript, I'm unable to directly use the provided code. I'm confused about how they&a ...