reactjsreduxkendo-uikendo-gridmemo

React.MEMO() fails to work with hooks inside of it (probably useDispatch) within KendoReact


I have a child filter input componenet inside kendo grid and my goa is stoping that components from rendering again and saving "input text" inside input field

   <GridColumn
      field="name"
      title="Document Name"
      headerCell={HeaderCell}
      className="tableCell"
      cell={LinkCell}
      filterCell={() =>  <SearchInput className={filtersToggler} /> }
      footerCell={(props) => <FooterCell {...props} colSpan={4} total={total}></FooterCell>}
    />

Now when I add some input inside that component, it passes value to the state called word and after 500 ms it triggers debounce and parses it to the redux state called "term"

const SearchInput = React.memo(() => {
  const [word, setWord] = useState('');

  const dispatch = useDispatch();

  const deb = useCallback(
    debounce((text) => dispatch({ type: 'SET_TERM', term: text }), 1000),
    []
  );

  const handleText = (text) => {
    deb(text);
  };
  return (
    <input
      className="searchInput"
      value={word}
      type="search"
      placeholder="Search.."
      onChange={(e) => {
        handleText(e.target.value)
        setWord(e.target.value);
      }}></input>
  );
});

export default SearchInput;

Now, whenever redux state changes, it triggers useEffect inside of a kendo grid and gets new data from API.

  const searchWord = useSelector((state) => state.search.term);
  const classifications = useSelector((state) => state.search.classifications);
  const date = useSelector((state) => state.search.date);

  useEffect(() => {
    const data = searchDocsByName(searchWord, date, classifications);
    data.then((i) => {
      setDocuments(i.data.data);
      setTotal(i.data.data.length);
    });
  }, [searchWord, date, classifications]);

So what's the problem? SearchInput Componenet rerenders even if its inside React.memo() and from the profiler I am getting that SearchInput rendered because "hook change".

I am totally stuck, I have no idea how to procced.


Solution

  • You needlessly set local state with const [word, setWord] = useState('');, setWord will re render your component because local state changed. You can make the input an uncontrolled component

    Here is an example of what you can do:

    const { Provider, useDispatch } = ReactRedux;
    const { createStore, applyMiddleware, compose } = Redux;
    const { useCallback } = React;
    
    const initialState = {};
    const reducer = (state, { type, term }) => {
      console.log('in reducer', type, term);
      return state;
    };
    //creating store with redux dev tools
    const composeEnhancers =
      window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    const store = createStore(
      reducer,
      initialState,
      composeEnhancers(
        applyMiddleware(
          () => (next) => (action) => next(action)
        )
      )
    );
    const debounce = (fn, time) => {
      let timer;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), time);
      };
    };
    const SearchInput = React.memo(function SearchInput() {
      console.log('rendering SearchInput');
      const dispatch = useDispatch();
      const deb = useCallback(
        debounce(
          (text) => dispatch({ type: 'SET_TERM', term: text }),
          1000
        ),
        []
      );
      return (
        <input
          className="searchInput"
          type="search"
          placeholder="Search.."
          onChange={(e) => {
            deb(e.target.value);
          }}
        ></input>
      );
    });
    
    const App = () => {
      return <SearchInput />;
    };
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
    
    <div id="root"></div>