Jake Jeon
Jake Jeon

Reputation: 151

React component doesn't get current state value in Event Listener function

I attached an Event Listener when TabularListPgination component mounts.

Then, I tried to access the current Redux state in Event Listener. However, I'm getting initial state of dataTable reducer instead of current state.

This same was working fine in Class based Component. Could you share your insight on this?

Sorry for my short in English.

import React, { useState, useEffect, useRef } from "react";
import { useSelector, useDispatch } from "react-redux";
import Paginate from "react-paginate";
import { loadDataTable } from "../../actions/index";

const TabularListPagination = () => {
    const dispatch = useDispatch();
    const dataTable = useSelector((state) => state.dataTable);

    useEffect(() => {
        attachShortcutEvent();
    }, []);

    const attachShortcutEvent = () => {
        document.addEventListener("keydown", (event) => {
            if (event.altKey && event.key === "ArrowLeft") {
                if (dataTable.current.current_page !== 1) {
                    dispatch(loadDataTable(dataTable.current.current_page - 1));
                }
            } else if (event.altKey && event.key === "ArrowRight") {
                if (dataTable.current.current_page !== dataTable.current.total_pages) {
                    dispatch(loadDataTable(dataTable.current.current_page + 1));
                }
            } else if (event.altKey && event.key === "Home") {
                dispatch(loadDataTable(1));
            } else if (event.altKey && event.key === "End") {
                dispatch(loadDataTable(dataTable.current.total_pages));
            }
        });
    };

    return (
        <Paginate
            containerClassName="pagination"
            forcePage={dataTable.current_page}
            pageCount={dataTable.total_pages}
            marginPagesDisplayed={2}
            pageRangeDisplayed={5}
            onPageChange={({ selected }) => {
                dispatch(loadDataTable(selected + 1));
            }}
            nextLabel="&gt;"
            previousLabel="&lt;"
        />
    );
};

export default TabularListPagination;

Upvotes: 2

Views: 890

Answers (1)

HMR
HMR

Reputation: 39270

Here is an example of how to implement according to my comment:

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;

const initialState = {
  dataTable: {
    current: { total_pages: 10, current_page: 1 },
  },
};
//action types
const GO = 'GO';
const FIRST = 'FIRST';
const LAST = 'LAST';
//action creators
const go = (direction) => ({
  type: GO,
  payload: direction,
});
const first = () => ({ type: FIRST });
const last = () => ({ type: LAST });
const reducer = (state, { type, payload }) => {
  if (type === GO) {
    const current_page =
      state.dataTable.current.current_page + payload;
    if (
      current_page < 1 ||
      current_page > state.dataTable.current.total_pages
    ) {
      return state;
    }
    return {
      ...state,
      dataTable: {
        ...state.dataTable,
        current: {
          ...state.dataTable.current,
          current_page,
        },
      },
    };
  }
  if (type === FIRST || type === LAST) {
    const current_page =
      type === FIRST
        ? 1
        : state.dataTable.current.total_pages;
    return {
      ...state,
      dataTable: {
        ...state.dataTable,
        current: {
          ...state.dataTable.current,
          current_page,
        },
      },
    };
  }
  return state;
};
//selectors
const selectDataTable = (state) => state.dataTable;
const selectCurrentDataTable = createSelector(
  [selectDataTable],
  (table) => table.current
);
//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 App = () => {
  const dispatch = useDispatch();
  const dataTable = useSelector(selectCurrentDataTable);

  const keyUp = React.useCallback(
    (event) => {
      //dispatching the actions are not depending on state
      if (event.altKey && event.key === 'ArrowLeft') {
        dispatch(go(-1));
      } else if (
        event.altKey &&
        event.key === 'ArrowRight'
      ) {
        dispatch(go(1));
      } else if (event.altKey && event.key === 'Home') {
        dispatch(first());
      } else if (event.altKey && event.key === 'End') {
        dispatch(last());
      }
      //only dep is dispatch but that never changes so keyUp is only
      //  created when component mounts. Added to dependency to silence
      //  linter (maybe updated version won't warn about dispatch)
    },
    [dispatch]
  );

  React.useEffect(() => {
    document.addEventListener('keydown', keyUp);
    //remove event listener when component is unmounted
    return () => document.removeEventListener(keyUp);
    //keyUp is a dependency but is only created on mount
  }, [keyUp]);

  return (
    <div>
      current page: {dataTable.current_page}
      total pages: {dataTable.total_pages}
    </div>
  );
};

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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>

Upvotes: 2

Related Questions