0

Whenever I update user (object) in users (array) in context - all components which uses users re-renders.


What I've tried

I have a component which is using values from context:

const DashboardCardList=()=> {
    const context = useContext(StaticsContext);
    const users = context.users.filter(user=>user.visible);

    return !users
        ? <Loading/>
        : (
            <Container>
                {users.map(user=>
                    <DashboardCard key={user._id} user={user}/>
                )}
            </Container>
        );
};

My update function (updates context state):

const onUserUpdate=(user)=>{
   const index = this.state.users.findIndex(item => item._id === user._id);
   const users = [...this.state.users]
   users[index] = user;
   this.setState({users:users});
}

Final component:

const DashboardCard=({user})=> {
    console.log("I'm here!", user);
    return ...;
}

Question

Why it keeps re-rendering? Is it because of context?
How to write this properly?

Tomás
  • 3,501
  • 3
  • 21
  • 38
Jax-p
  • 7,225
  • 4
  • 28
  • 58
  • Looks like you got a typo: `findIndex(item => item._id === item._id);` – Brian Lee Nov 24 '20 at 11:35
  • Yes, thats the "problem" with context. It renders every component every time it changes. Thats why redux or mobx became so popular. But if you keep context small enough or create mutliple ones, it should not cause problems. If you do, try to use redux/mobx and see if that changes it. – Domino987 Nov 24 '20 at 11:35
  • 1
    @Domino987 I see. Does it render only when you are using some of explicit context values (like _users_) or whenever I use `useContext`? Might be `React.memo` or something like it useful here? Redux seems to be a little bit overkilll for my project atm. – Jax-p Nov 24 '20 at 11:41
  • @Jax-p Reactm.memo only works for props, not for the useContext hook, but there are ways, Read more [here](https://kentcdodds.com/blog/application-state-management-with-react). But if you do not see any problems, the rerenders are not a problem, especially if the project is small. Reredners are not a problem in itself, as long as it does not slow down your website. Also maybe look into zustand or recoil where the setup is minimal. – Domino987 Nov 24 '20 at 11:44
  • @Domino987 _Reactm.memo only works for props_; So it means if I would implement Memo in DashboardCard (which recieves _users_ prop) it could be working right? Performance is not problem here. It's websocket app and every re-render is stealing another users focus etc. – Jax-p Nov 24 '20 at 11:50
  • 1
    Yes for that, it would work on the `DashboardCard` level – Domino987 Nov 24 '20 at 11:51

1 Answers1

1

There is no render bailout for context consumers (v17).

Here is a demonstration, where the Consumer will always re-render just because he is a Context Consumer, even though he doesn't consume anything.

import React, { useState, useContext, useMemo } from "react";
import ReactDOM from "react-dom";

// People wonder why the component gets render although the used value didn't change
const Context = React.createContext();

const Provider = ({ children }) => {
  const [counter, setCounter] = useState(0);
  const value = useMemo(() => {
    const count = () => setCounter(p => p + 1);
    return [counter, count];
  }, [counter]);
  return <Context.Provider value={value}>{children}</Context.Provider>;
};

const Consumer = React.memo(() => {
  useContext(Context);
  console.log("rendered");
  return <>Consumer</>;
});

const ContextChanger = () => {
  const [, count] = useContext(Context);
  return <button onClick={count}>Count</button>;
};

const App = () => {
  return (
    <Provider>
      <Consumer />
      <ContextChanger />
    </Provider>
  );
};

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

Edit Context API Problem

To fix it:

  • Use a single context for each consumed value. Meaning that context holds a single value, (and no, there is no problem with multiple contexts in an application).
  • Use a state management solution like Recoil.js, Redux, MobX, etc. (although it might be overkill, think good about app design beforehand).
  • Minor optimization can be achieved by memoizing Provider's values with useMemo.
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • Thank you for tips. Yeah Redux seems to be overkill for this one. As far as I understand your first point I could have a single context for each user or _createStore()_ for each user with _reusable_ library - right? The problem is that context doesn't store only users. It stores also another entities (projects, clients). It might get a little bit over-contexted, doesn't it? – Jax-p Nov 24 '20 at 11:47
  • The problem is that developers put everything in a single context, just split it according to your needs. And stop worrying about rerenders without having real performance problems, typical apps are not - big - used - enough to even consider to think about performance – Dennis Vash Nov 24 '20 at 11:49
  • Well but I use all of these values at the same time - all the time. That's why they are in same context. So does it mean if I have 40 users - i have 40 contexts? Performance is not problem here. It's websocket app and every re-render is stealing another users focus etc. – Jax-p Nov 24 '20 at 11:51
  • Don't know what "stealing another users focus" means, but if you memoize the dumb components (the ones that don't consume context) the UI won't rerender hence users shouldn't notice any change on screen. – Dennis Vash Nov 24 '20 at 11:53
  • It means stealing focus from input while they are writing. Destroying drag and drop context while they are drag and dropping something and etc. Ok, I'll do separate context for users, projects, functions etc. But could you explain the context spliting please? As I've asked did you mean: "if I have 40 users - i have 40 contexts?" – Jax-p Nov 24 '20 at 11:56
  • 1
    No, the problem you describing is that you have components that listen to data AND render UI, it should be separated, components to get data and components ONLY for view, that way the UI won't flicker when no needed. – Dennis Vash Nov 24 '20 at 11:57
  • "Use a single context for each consumed value" - while this may be best practices, for components where you need to have state tracked very deeply, this is going to lead to some ugly declarative code, with contexts wrapping contexts – Steven Matthews Nov 27 '22 at 22:47