0

Specifically with Material UI, I'm trying to render a large number of inputs (> 100) and manage a global state effectively. I run into serious performance issues like input lag at around 50 inputs but the lag is still noticeable at around 10 inputs. I've narrowed it down to the Material UI <TextField /> component because if I change that to regular input element, there is virtually no lag which is to be expected since there's a lot more happening in a MUI component.

What I don't know however, is if changing how I manage state might help performance. I've tried 2 state management approaches: traditional react with useState hook and Recoil.

In addition to the 2 approaches above, I also tried a combination of recoil and traditional react by using useState and useSetRecoilState in filter components so that all filters wouldn't re-render on change to recoil state since they are only writing not reading but still no luck.

EDIT
Turns out I forgot to test putting values in more than 1 input... When you enter a value in another input, the previously entered input will get wiped. I'm thinking it's because in my handleChange function, I replace an item in filterList but filterList is now an old outdated reference so it does not have the previously set value. Back at square one now, because if I read state in the filter component, then the component will re-render which eliminates the memo performance boost.

EDIT 2
Posted solution below

cap
  • 337
  • 3
  • 14
  • I suggest to write a real answer your own question, which is [allowed on stackoverflow](https://stackoverflow.com/help/self-answer), so that it is not displayed as "unanswered". – kca Mar 13 '22 at 10:14

1 Answers1

0

Looks like I was trying to use recoil when I really didn't need to. Using React.memo along with React.useState and moving the global state edit up to the parent ended up being all I needed.

Filters.js

const filterMaker = (length) => {
  let filters = [];
  for (let i = 0; i < length; i++) {
    if (i % 2 === 0) {
      filters.push({ name: `filter-${i}`, type: 'a', active: { value1: '' } });
    } else {
      filters.push({
        name: `filter-${i}`,
        type: 'b',
        active: { value1: '', value2: '' },
      });
    }
  }
  return filters;
};

function replaceItemAtIndex(arr, index, newValue) {
  return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}

export default function Filters() {
  const [filterList, setFilterList] = useRecoilState(filterListState);

  useEffect(() => {
    setFilterList(filterMaker(200));
  }, []);

  const handleChange = (filter, name, value, index) => {
    setFilterList((prevState) => {
      return replaceItemAtIndex(prevState, index, {
        ...filter,
        active: {
          ...filter.active,
          [name]: value,
        },
      });
    });
  };

  return (
    <div>
      {filterList.map((filter) =>
        filter.type === 'a' ? (
          <FilterA
            key={filter.name}
            filter={filter}
            filterList={filterList}
            handleChange={handleChange}
          />
        ) : (
          <FilterB
            key={filter.name}
            filter={filter}
            filterList={filterList}
            handleChange={handleChange}
          />
        )
      )}
    </div>
  );
}

FilterA.js

export const FilterA = memo(
  ({ filter, filterList, handleChange }) => {
    const [values, setValues] = useState(filter.active || {});
    const index = filterList.findIndex((item) => item.name === filter.name);

    const handleLocalChange = ({ target: { name, value } }) => {
      setValues((prevState) => {
        return { ...prevState, [name]: value };
      });
      handleChange(filter, name, value, index);
    };

    return (
      <Box mb={1}>
        <Typography>{filter.name}</Typography>
        <TextField
          variant="standard"
          name="value1"
          label="value1"
          value={values.value1 || ''}
          onChange={handleLocalChange}
        />
      </Box>
    );
  },
  (prevProps, nextProps) => {
    if (prevProps.filter.active.value1 === nextProps.filter.active.value1) {
      return true;
    }
    return false;
  }
);
cap
  • 337
  • 3
  • 14