Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TablePagination warning when page is out of range #15616

Closed
eps1lon opened this issue May 7, 2019 · 39 comments
Closed

TablePagination warning when page is out of range #15616

eps1lon opened this issue May 7, 2019 · 39 comments
Labels
component: table This is the name of the generic UI component, not the React module! discussion

Comments

@eps1lon
Copy link
Member

eps1lon commented May 7, 2019

This change adds a warning in a common use case.

Let's say a user is displaying a list with two pages. The user goes to page 2. Then, he chooses to refresh the page. The console shows a warning:

Warning: Failed prop type: Material-UI: the page prop of a TablePagination is out of range (0 to 0, but page is 1).

This is because, when the user refreshes the page, the list is empty (count=0) until the data is loaded. This "loading" state fails the page prop validation, which creates a warning.

This used to work well in v1, I'm not sure this regression is a desired effect.

Originally posted by @fzaninotto in #14534 (comment)

@eps1lon eps1lon added component: table This is the name of the generic UI component, not the React module! discussion labels May 7, 2019
@eps1lon
Copy link
Member Author

eps1lon commented May 7, 2019

This is a question where prop validation should reside. In this case I think app code is more appropriate. The library can't know if the current value will make sense later. Checking for 0,0 is probably a good enough heuristic but it's yet again another heuristic the user has to know.

I think it's better that the app dev decides what fallback value should be used (min, max etc) until the data is loaded rather than the library. I'm inclined to believe that the warning is fine here. It's exactly the reason it is only a warning: Your code might not work but we can't be sure so we warn instead of throwing.

@fzaninotto Could you include a codesandbox that illustrates the use case?

@fzaninotto
Copy link
Contributor

Here is the CodeSandbox: https://codesandbox.io/s/zkqpo139rx

@eps1lon
Copy link
Member Author

eps1lon commented May 7, 2019

Here is the CodeSandbox: codesandbox.io/s/zkqpo139rx

That doesn't really help me understand the issue. The decision to warn against out-of-range pages was a conscious one. What I'm missing is are use cases where this is accepted behavior and why the warning is appropriate in those cases.

@fzaninotto
Copy link
Contributor

I've modified the code. Is it more explicit?

@oliviertassinari
Copy link
Member

I agree with @eps1lon, I think the the warning is correct here. It's the least worse solution to the problem. @fzaninotto Do you have a better alternative in mind?

@fzaninotto
Copy link
Contributor

Not warning at all if the page is out of bounds when the count is zero seems a reasonable trade off to me.

@oliviertassinari
Copy link
Member

oliviertassinari commented May 7, 2019

@fzaninotto In your example, have you considered?

start from 0

const PaginationWithLoading = () => {
  const [count, setCount] = useState(-1);
  // simulate loading
  useEffect(() => {
    setTimeout(() => setCount(15), 1000);
  });
  return (
    <TablePagination
      page={count === -1 ? 0 : 1}
      count={count === -1 ? 0 : count}
      rowsPerPage={10}
      onChangePage={() => null}
    />
  );
};

or

start from page 1

const PaginationWithLoading = () => {
  const [count, setCount] = useState(-1);
  // simulate loading
  useEffect(() => {
    setTimeout(() => setCount(15), 1000);
  });
  return (
    <TablePagination
      page={1}
      count={count === -1 ? 1 * 10 + 1 : count}
      rowsPerPage={10}
      onChangePage={() => null}
    />
  );
};

@fzaninotto
Copy link
Contributor

Not sure I understand. You suggest to pass a fake but valid count to TablePagination with the data is loading?

@oliviertassinari
Copy link
Member

oliviertassinari commented May 7, 2019

@fzaninotto I'm suggesting to fully assume the table state, either we don't know anything and we display 0 or we optimistically assume the page contains items.

@fzaninotto
Copy link
Contributor

Interesting. I made the choice to hide the TablePagination during loading, but that creates a flickering effect when the loading ends and the pagination appears. Maybe showing an optimistic pagination would be less perturbing. I'll test that on a few users.

It boils down to designing the loading state. That's something the Material Design specification doesn't address if I'm not mistaken, and it's always a hard task.

@eps1lon
Copy link
Member Author

eps1lon commented May 8, 2019

To reiterate on another point that was addressed by this change: Passing an out-of-range page could cause bugs depending on how onPageChange was used. onPageChange used to be called on every render if the page was out-of-range.

@oliviertassinari
Copy link
Member

@fzaninotto Have you found an acceptable solution to the problem?

@fzaninotto
Copy link
Contributor

Yes, I've hidden the pagination while loading. It adds a flicker, but there is one anyway (since the total number of results changes).

Thanks for your help.

@eps1lon
Copy link
Member Author

eps1lon commented May 9, 2019

Yes, I've hidden the pagination while loading.

Why not just check against an out-of-range page or use page={0} for the loading state?

@fzaninotto
Copy link
Contributor

Why not just check against an out-of-range page or use page={0} for the loading state?

Displaying an out of range page or a wrong page number during loading is worse than displaying nothing IMO, because it's showing information that we know are wrong.

@shkyung
Copy link

shkyung commented Feb 19, 2020

is it Fixed in v4.9.3 ????
I have some trouble dealing with the issues...

still there is error at the latest version.. fix properly please

@mosaiceye
Copy link

If you do this:
useEffect(() => { if( data.length === rowsPerPage && page > 0 ) { setPage(0); } }, [data.length, rowsPerPage, page]);

And this:
<TableFooter> <TableRow> <TablePagination rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]} colSpan={3} count={data.length} rowsPerPage={rowsPerPage} page={ ( page > 0 && data.length === rowsPerPage ) ? 0 : page } SelectProps={{ inputProps: { 'aria-label': 'rows per page' }, native: true, }} onChangePage={handleChangePage} onChangeRowsPerPage={handleChangeRowsPerPage} ActionsComponent={TablePaginationActions} /> </TableRow> </TableFooter>

Then it fixes the issue.

@mosaiceye
Copy link

Set your page property on your like this:
page={ ( page > 0 && data.length === rowsPerPage ) ? 0 : page }

@lostmarinero
Copy link

Have you all considered if someone has a largeish data set and wants to be able to start in the middle, how to handle it? For example, if I have 1000 records and i do the page length of 50 per page, and i want to start at page 10 (without having to pre-load 500 records), how could i accomplish this?

I was trying to do so with using queryParams (pageNumber), and then loading the page via the api, however if I use pageNumber in with the TablePagination, I get:
index.js:1 Warning: Failed prop type: Material-UI: the page prop of a TablePagination is out of range (0 to 0, but page is 1).

@oliviertassinari
Copy link
Member

@lostmarinero Do you have a small reproduction we could look at? The TablePagniation component is only concerned with the display, you could "lies" to him around what data is available.

@AbdielAldana94
Copy link

So still no stable solution found?

@Cristy94
Copy link

Cristy94 commented Apr 9, 2020

Maybe related: mbrn/material-table#955 (for me, the data changes when I'm on page 2, and the table doesn't update the page when data is changed, so the page is now out of bounds).

@Bro3Simon
Copy link

Yes, I've hidden the pagination while loading. It adds a flicker, but there is one anyway (since the total number of results changes).

Thanks for your help.

Can you show how you hid the pagination on load?

@marko3190
Copy link

marko3190 commented Feb 13, 2021

Get the same error and it was pretty simple, modify code like that, its work for me but i get count from redux.

if(count) {
  return (
    <TablePagination
          page={1}
          count={count === -1 ? 1 * 10 + 1 : count}
          rowsPerPage={10}
          onChangePage={() => null}
        />
  )
}
return null

@marko3190
Copy link

page={(page > 0 && items.length < rowsPerPage) ? 0 : page}

@AbdoMoh96
Copy link

onPageSizeChange={(params) => params.page = 1 }

@fluxquantum
Copy link

fluxquantum commented Mar 17, 2021

onPageSizeChange={(params) => params.page = 1 }
@AbdoMoh96 Thanks for sharing this.
Where does 'params' come from in this case? Also what version are you using? The property doesn't seem to exist in my version of material ui ("^4.11.3")

@fluxquantum
Copy link

fluxquantum commented Mar 17, 2021

If you do this:
useEffect(() => { if( data.length === rowsPerPage && page > 0 ) { setPage(0); } }, [data.length, rowsPerPage, page]);

And this:
<TableFooter> <TableRow> <TablePagination rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]} colSpan={3} count={data.length} rowsPerPage={rowsPerPage} page={ ( page > 0 && data.length === rowsPerPage ) ? 0 : page } SelectProps={{ inputProps: { 'aria-label': 'rows per page' }, native: true, }} onChangePage={handleChangePage} onChangeRowsPerPage={handleChangeRowsPerPage} ActionsComponent={TablePaginationActions} /> </TableRow> </TableFooter>

Then it fixes the issue.

@mosaiceye

Thanks for sharing your approach. Could you provide more detail about how you're passing around the state of 'data'? I currently have a bunch of handler functions to update the current page. How are your handler methods working with the useEffect hook? I took a stab leveraging your solution but so far haven't been able to reset the page.

I am doing something like

const [page, setPage] = useState(DEFAULT_START_PAGE_INDEX);
const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_ROWS_PER_PAGE);

const handleChangePage = (event, newPage) => {
    setPage(newPage);
};

const handleChangeRowsPerPage = (event) => {
    setRowsPerPage(parseInt(event.target.value, DEFAULT_ROWS_PER_PAGE));
    setPage(DEFAULT_START_PAGE_INDEX);
};

const [totals, setTotals] = useState([{}]);
    useEffect(() => {
        setTotals(props.totalReported ? props.totalReported : 0)
    }, [props.totalReported])

const [caseItems, setCaseItems] = useState([{}]);
useEffect(() => {
    setCaseLookupResults(props.caseItems ? props.caseItems : [{}])
}, [props.items])

useEffect(() => { 
        if( ( props.items && totals === rowsPerPage && page > 0 ) ) { 
            setPage(0); 
        } 
    }, [totals, rowsPerPage, page]);

<TablePagination
        component="div"
        count={caseLookupTotals}
        page={( page > 0 && totals === rowsPerPage ) ? 0 : page}
        onChangePage={handleChangePage}
        rowsPerPage={rowsPerPage}
        onChangeRowsPerPage={handleChangeRowsPerPage}
/>

@fluxquantum
Copy link

Yes, I've hidden the pagination while loading. It adds a flicker, but there is one anyway (since the total number of results changes).

Thanks for your help.

Hi @fzaninotto , thanks for sharing your approach, I'd like to try out the various strategies in this thread. May you give a little more detail on how you hide the pagination while loading the data? What part of the component is that custom method embedded? Thank you for your time.

@sytpb
Copy link

sytpb commented Apr 15, 2021

what 's solution finally ? I have try many ways , it doesn't work .

@mlg87
Copy link

mlg87 commented Apr 21, 2021

@marko3190

page={(page > 0 && items.length < rowsPerPage) ? 0 : page}

this wont work when you are on the last page of a set that has fewer items than what youre paginating by

@berzi
Copy link

berzi commented Feb 15, 2022

I have an issue with this feature. I implemented custom pagination because I need the results to be paginated server-side, but I still use the pagination option because it's convenient. To make it work, however, I manage the next and previous page buttons myself (the table only receives 50 items, but I know that the server has a total of 60, so even with rowsPerPage=50 the next page button is clickable). When I go to the next page, everything does work well, but I get a console warning because the table thinks there is only one page, and so the current page is out of range.

I would like a way to artificially tell the table how many pages I think there are, or to silence this warning, since I know that the range is going to be out of sync with the current page, and it is expected behaviour.

@LBeckX
Copy link

LBeckX commented Jul 27, 2022

const handlePageSize = (elementAmount: number): void => {
        const maxPage = (Math.floor(elementAmount / rowsPerPage) - 1);
        if (page >= maxPage) {
            setPage(maxPage);
        }
 }

@dtrenz
Copy link

dtrenz commented Nov 28, 2022

Just came across this and resolved like so:

<TablePagination
   onPageChange={handlePageChange}
   page={rowsPerPage >= count ? 0 : page}
/>

@humoyun91
Copy link

humoyun91 commented Dec 8, 2022

Interesting. I made the choice to hide the TablePagination during loading, but that creates a flickering effect when the loading ends and the pagination appears. Maybe showing an optimistic pagination would be less perturbing. I'll test that on a few users.

It boils down to designing the loading state. That's something the Material Design specification doesn't address if I'm not mistaken, and it's always a hard task.

Maybe this might be useful in some way.
We also had a flickering effect for Pagination part of DataGrid (displaying unwanted initial value 0 - 0 while fetching data). Then, we decided to customize TablePagination with loading state which we can be passed to DataGrid in order to prevent perturbing flickering effect.

Here are some snapshots:

Normal state Loading state
Normal Loading
      <DataGrid
        ...
        components={{
          Pagination: () => (
            <LoadableTablePagination
              loading={isLoading}
              onRowsPerPageChange={handlePageSizeChange}
              onPageChange={handlePageChange}
              count={count}
              rowsPerPage={filterParams.page_size}
              page={filterParams.page - 1}
              rowsPerPageOptions={[10, 20, 50]}
            />
          ),
        }}

Here is the LoadableTablePagination:

import CircularProgress from '@mui/material/CircularProgress';
import TablePagination, { TablePaginationProps } from '@mui/material/TablePagination';
import { styled } from '@mui/material/styles';

type PaginationProps = TablePaginationProps & {
  loading: boolean;
};

const LoadableTablePagination = ({
  page,
  loading,
  count,
  rowsPerPage,
  rowsPerPageOptions,
  onPageChange,
  onRowsPerPageChange,
}: PaginationProps): JSX.Element => {
  return (
    <TablePagination
      rowsPerPageOptions={rowsPerPageOptions}
      component="div"
      count={count}
      page={count <= 0 ? 0 : page}
      rowsPerPage={rowsPerPage}
      onPageChange={onPageChange}
      onRowsPerPageChange={onRowsPerPageChange}
      labelDisplayedRows={({ from, to, count }) =>
        !loading ? (
          <StyledDisplayedRows>{`${from}–${to} of ${count !== -1 ? count : `more than ${to}`}`}</StyledDisplayedRows>
        ) : (
          <StyledLoadingBox>
            <CircularProgress size={16}></CircularProgress>
          </StyledLoadingBox>
        )
      }
    />
  );
};

const StyledDisplayedRows = styled('span', { name: 'StyledDisplayedRows' })(() => ({
  display: 'inline-flex',
  minWidth: 100,
}));

const StyledLoadingBox = styled('span', { name: 'StyledLoadingBox' })(() => ({
  display: 'inline-flex',
  justifyContent: 'center',
  width: 100,
}));

export default LoadableTablePagination;

@ankursaxena-nickelfox
Copy link

I also came across this issue and found this issue thread where multiple solutions were suggested which I tried one-by-one but nothing really worked 😄.
Well this doesn't mean solutions provided here are not correct instead might not be suitable in my case or I might not be able to apply them efficiently 😋.

But below is the easy solution which I used and worked pretty well

  • In my case, I found the issue was occurring whenever I was on page number 2 or more and was applying a filter on my data but the resultant data did not have any rows to show.
  • So in such case page number used to remain on whatever page it was but table data forces to remain on 0 page.
  • All I did was reset the page number to 0 in the same function where I was applying filter and setting new data.
  • In my case, I was using redux to handle states so I moved the page's state also inside redux and pushed 0 as page state whenever I was updating my data. (Isn't this simple)

@abiZsrm
Copy link

abiZsrm commented Apr 20, 2023

Just came across this and resolved like so:

<TablePagination
   onPageChange={handlePageChange}
   page={rowsPerPage >= count ? 0 : page}
/>

This was superhelpful.

@khangahs
Copy link

Just came across this and resolved like so:

<TablePagination
   onPageChange={handlePageChange}
   page={rowsPerPage >= count ? 0 : page}
/>

This help me, thank you kind stranger.

@vsDizzy
Copy link

vsDizzy commented Dec 21, 2023

Better solution:

<TablePagination
   onPageChange={handlePageChange}
   page={count && page}
/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: table This is the name of the generic UI component, not the React module! discussion
Projects
None yet
Development

No branches or pull requests