Search code examples
reactjstypescriptreact-typescriptfluent-uifluentui-react

Why is the component fully re-rendering when updating a single state through context?


I have created a page which has two columns:

  1. In one column the idea is to display a list of items
  2. On the other column, I should show some info related to the selected item

The code I have so far is:

import { INavLink, INavLinkGroup, INavStyles, Nav } from "@fluentui/react";
import React, { createContext, useContext, useState } from "react";

interface HistoryTtem {
  id: string;
}

interface AppState {
  selectedItem: string | undefined;
  updateSelectedItem: (value: string | undefined) => void;
  items: Array<HistoryTtem>;
}

const AppContext = createContext<AppState>({
  selectedItem: undefined,
  updateSelectedItem: (value: string | undefined) => {},
  items: []
});

const App = () => {
  const Column1 = () => {
    const rootState: AppState = useContext(AppContext);

    const getNavLinks: Array<INavLink> = rootState.items.map((item) => ({
      name: item.id,
      key: item.id,
      url: ""
    }));

    const groups: Array<INavLinkGroup> = [
      {
        links: getNavLinks
      }
    ];

    const navStyles: Partial<INavStyles> = {
      root: {
        boxSizing: "border-box",
        border: `1px solid #eee`,
        overflowY: "auto"
      }
    };

    const onItemClick = (
      e?: React.MouseEvent<HTMLElement>,
      item?: INavLink
    ) => {
      if (item && item.key) {
        rootState.updateSelectedItem(item.key);
      }
    };

    return (
      <Nav
        onLinkClick={onItemClick}
        selectedKey={rootState.selectedItem}
        ariaLabel="List of previously searched transactions"
        styles={navStyles}
        groups={groups}
      />
    );
  };

  const Column2 = () => {
    return <div>aaa</div>;
  };

  const [historyItems, setHistoryItems] = useState<Array<HistoryTtem>>([
    {
      id: "349458457"
    },
    {
      id: "438487484"
    },
    {
      id: "348348845"
    },
    {
      id: "093834845"
    }
  ]);
  const [selectedItem, setSelectedItem] = useState<string>();

  const updateSelectedItem = (value: string | undefined) => {
    setSelectedItem(value);
  };

  const state: AppState = {
    selectedItem: selectedItem,
    updateSelectedItem: updateSelectedItem,
    items: historyItems
  };

  return (
    <AppContext.Provider value={state}>
      <div>
        <Column1 />
        <Column2 />
      </div>
    </AppContext.Provider>
  );
};

export default App;

As you can see, I have a root state which will serve to drive the update of the second column triggered from inside the first one. But it is not working. When I click on an item, the whole component in the first column is re-rendering, while it should only change the selected item.

Please find here the CodeSandbox.


Solution

  • You shouldn't nest component functions.

    The identity of Column1 changes for every render of App since it's an inner function, and that makes React think it needs to reconcile everything.

    Move Column1 and Column2 up to the module level.