2

I'm experimenting with new Context API and hooks. I've created an app with sidebar (treeview), footer and main content page. I have a context provider

const ContextProvider: FunctionComponent = (props) => {

const [selected, setSelected] = useState(undefined);
const [treeNodes, setTreeNodes] = useState([]);

return (
    <MyContext.Provider
        value={{
            actions: {
                setSelected,
                setTreeNodes
            },
            selected,
            treeNodes
        }}
    >
        {props.children}
    </MyContext.Provider>
);

Im my content component I have a DetailsList (Office Fabric UI) with about 1000 items. When I click on the item in the list I want to update selected item in context. This works but it is really slow. It takes about 0,5-1 seconds to select item in the list. The list is virtualized. I have tried it on production build. Thing are a bit better but there is a noticable lag when clicking on list. Footer is consuming myContext to display information about selected item.

Here is a bit of code from my component

const cntx = useContext(MyContext);

const onClick = (item) => {
    cntx.actions.setSelected(item);
};

Am I using the context wrong?

I've created a sample sandbox to demonstrate.. You can scroll to about 100-th index and click a couple of times to see how it gets unresponsive.

https://codesandbox.io/s/0m4nqxp4m0

Is this a problem with Fabric DetailsList? Does it reRender to many times? I believe the problem is with "complex" DatePicker component but I don't understand why does DetailsList get rerenderd? It's not using any of context properties within a render function. I would expect only Footer component to rerender on every context change

partyelite
  • 822
  • 1
  • 15
  • 26
  • There is a lot of ``"The icon "calendar" was used but not registered. See http://aka.ms/fabric-icon-usage for more information. "`` warnings in console - im worried that this logging alone is causing the problem - please check that :) #edit - https://codesandbox.io/embed/llj1zk19rz – r g Mar 20 '19 at 08:18
  • have you found a solution? I'm facing the same issue and the answer from @sonicmario is not helping – bieboebap Dec 23 '22 at 00:38

3 Answers3

5

Caveats Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:

class App extends React.Component {
  render() {
    return (
      <Provider value={{something: 'something'}}>
        <Toolbar />
      </Provider>
    );
  }
}

To get around this, lift the value into the parent’s state:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

https://reactjs.org/docs/context.html#caveats

bieboebap
  • 320
  • 3
  • 18
sonicmario
  • 601
  • 8
  • 22
1

Memorize your selected item with useMemo to avoid creating an new object if you are referring to the same item. Then pass it in a dedicated context

Libert Piou Piou
  • 540
  • 5
  • 22
-1

In your solution, whenever your component is rerendered, a new instance of value is passed down as a prop. This will trigger the re-rendering of the children as well.

If you use hooks, to prevent this, memoize the value object that you want to pass, in the useMemo or useCallback hook. In fact, this should be applied as a basic React practice to any of your components, not just a context component. Unless you're passing a primitive value (string, number, ...), don't create an instance or an inline function directly and pass it as a prop. Make sure the props you're passing don't get changed after each rendering cycle of the React component.

allicanseenow
  • 123
  • 1
  • 10