How to paginate data? - reactjs

How can I implement pagination to display 10 objects on a page? Here's my sandbox:
https://codesandbox.io/s/rvo801wyp

There's two things to do:
You need to either bind handlePageChange in the constructor or convert it to an arrow function
Add a slice to remove from items the data that's not going to be shown .slice(this.state.activePage * this.state.itemsCountPerPage,(this.state.activePage + 1) * this.state.itemsCountPerPage)
Take a look to my changes in the code:
https://codesandbox.io/s/k58z3r9115
I've also added dynamically itemsCountPerPage to the state

There are two ways to achieve this objective,
Pagination using API
make API to handle some request param like
{
**per_page_limit**: <no of record on a single page>,
**current_page**: <current page number>,
....other request param
}
Pagination on client end
fetch all the records and create a pagination, for this get length of list and on bases of your requirement create pages,
for eg. total record: 100
per page limit: 10, so total pages will be 10
now to show only data for page, use setState, and for data use
list_data_to_display = list.slice(0, page*limit)
this.setState({slow_list: list_data_to_display})
handlePageChange=(pageNumber)=> {}
Solution will be:
this.state = {
items: [],
isLoading: false,
activePage: 1,
itemsCountPerPage: 1,
totalItemsCount: 1,
**show_list**: []
};
show_list_data = () => {
const show_list_data = this.state.items.slice(0, 10);
this.setState({
show_list: show_list_data
});
};
componentDidMount() {
fetch("https://demo9197058.mockable.io/users")
.then(res => res.json())
.then(json => {
this.setState(
{
items: json,
isLoading: true
},
this.show_list_data
);
});
}
handlePageChange = pageNumber => {
console.log(`active page is ${pageNumber}`);
const show_list_data = this.state.items.slice(
(pageNumber - 1) * 10,
pageNumber * 10
);
this.setState({
show_list: show_list_data,
activePage: pageNumber
});
};
render() {
var { show_list, isLoading } = this.state;
if (!isLoading) {
return <div> loadding....</div>;
} else {
return (
<div>
<ul>
{show_list.map(item => (
....
....

Related

RTK query use infinity scroll and pagination at the same time

Describtion:
I am trying to use infinity scroll and pagination at the same time.
I have a component which loads the user orders with infinity scroll and another component in different page which loads user order by pagination.
after i read the documentation I noticed we could use serializeQueryArgs, merge, forceRefetch to create these effects so I did it in my api slice.
Problem:
I have nice infinity scroll but my pagination works just like my infinity scroll
every time I click on a page the new data merged with my prev page.
so I decided to use extera prams for my query just only for serializeQueryArgs mehtod and check if I call this api from the component with infinity scroll or pagination and it works better and now i have another problem for example when I'm in the page 1 and go to page 2 everything seems normal but when i back to page 1 i have duplicated the page one array.
here is my api Slice:
export const orderApi = ApiSlice.injectEndpoints({
tagTypes: ['Orders'],
endpoints: (builder) => ({
getOrdersCount: builder.query({
query: ({ page, size, other }) => ({
url: `/order/orders/count?${page ? `page=${page}` : ''}${
size ? `&size=${size}` : ''
}${other || ''}`,
method: 'GET'
}),
transformResponse: (res) => res // just only one number
}),
getOrderList: builder.query({
providesTags: (result) => {
return result.map((item) => ({ type: 'Orders', id: item.id }));
},
async queryFn({ page, size, other }) {
const portfolioList = await axios.get(
`/order/orders?${page ? `page=${page}` : ''}${size ? `&size=${size}` : ''}${
other || ''
}`
);
if (portfolioList.error) return { error: portfolioList.error };
const endpoints = portfolioList.data.map((item) =>
axios.get(`/market/instruments/${item.insRefId}`)
);
let error = null;
let data = null;
try {
data = await Promise.all(endpoints).then((res) => {
return res.map((item, index) => {
return { ...item.data, ...portfolioList.data[index] };
});
});
} catch (err) {
error = err;
}
return data ? { data } : { error };
},
// Only have one cache entry because the arg always maps to one string
serializeQueryArgs: ({ endpointName, queryArgs }) => {
const { infinity, page, size } = queryArgs;
if (infinity) return endpointName;
return endpointName + page + size;
},
// Always merge incoming data to the cache entry
merge: (currentCache, newItems) => {
currentCache.push(...newItems);
},
// Refetch when the page arg changes
forceRefetch({ currentArg, previousArg }) {
console.log('currentArg', currentArg);
return currentArg !== previousArg;
},
})
})
});
any solution or best practice ?

useInfiniteQuery customization

I have to customize useInfiniteQuery from reactQuery in order to get data from all the pages.
https://codesandbox.io/s/cocky-http-zjpff?file=/pages/index.js:961-1025
This is the sandbox implementation available in the documentation in case you need further context on it.
My Api response looks like this.
{
list:{
data1: {...data},
data2: {...data},
...
}
totalPages: 3,
totalCount: 15,
limit: 5
}
Here data1, data2 are just examples of data returning in the list and backend is returning the data in pages, every page is unique.
What I have written yet.
export const useGetAllContactUsRequest = (
limit = 10,
unread = false
): UseInfiniteQueryResult<GetAllContactUsRequestResponse, AxiosError> => {
let page = 1;
return useInfiniteQuery(
["getAllContactUsRequest", unread],
({ pageParam = page }) => getAllContactUsRequest(pageParam, limit, unread),
{
refetchOnWindowFocus: false,
getNextPageParam: (lastPage) => {
console.log(page);
page++;
return lastPage.totalPages >= page ? page : undefined;
},
}
);
};
Essentially what I want is to increment the page and get data until the last page available that is page = 3 in my case.
This is how I am consuming it.
const {
data: readList,
isSuccess: readListSuccess,
hasNextPage: readListHasNextPage,
fetchNextPage: readListFetchNextPage,
} = useGetAllContactUsRequest();
console.log(readListHasNextPage);
This is a function only triggered when the user scrolls at the bottom of the page.
const onScrollLoadMore = () => {
if (readListHasNextPage) {
readListFetchNextPage();
}
}
The current behavior is that it randomly consoles the page numbers from 1 to 4 and hasNextPage criteria always return true.
I need help, it will be appreciated greatly! I have followed up on the documentation and it is confusing.

Why only one button?

I make a request to my local server using fetch() method. The server returns this response:
{
// total quantity elements in all page
"total":7,
// quantity elements in one page
"perPage":3,
// current page
"page":1,
// quantity page
"lastPage":3,
// it category list. I display my category list on page.
"data":[
{"id":1,"title":"animals","created_at":"/...","updated_at":"/..."},
{"id":2,"title":"space","created_at":"/...","updated_at":"/..."},
{"id":3,"title":"sport","created_at":"/...","updated_at":"/..."}
]
}
Also in my local server, I have ability to use query parameters page or limit which I insert in URL:
page - using this param I can implement pagination
limit - using this param I can implement choose quantity element
I have two tasks:
Make pagination (DONE)
Make ability to choose quantity element on page using three buttons (quantity three elements, quantity four elements, quantity five elements)
First task I've already done, however the second task is where I have a problem.
Instead of three buttons, I have only one button. When I click this button in my page it displays two elements. I also need to show the other 2 buttons which will display three and four elements respectively when clicked.
See screenshot:
What to fix in the code?
Maybe I wrote something wrong in the return?
I comment code line which implement choose quantity element
Home.js:
const Home = () => {
const [value, setValue] = useState({
listCategory: [],
currentPage: 1,
buttonsPagination: 0,
quantityElementPage: 3, // this line
buttonsQuantityElementPage: 3 // this line
});
useEffect(() => {
async function fetchData(currentPage, quantityElementPage) { // this line
try {
const res = await apiCategory('api/categories', {
method: 'GET',
}, currentPage, quantityElementPage ); // this line
console.log(res);
setValue({
listCategory: res.data,
currentPage: res.page,
buttonsPagination: Math.ceil(res.total / res.perPage),
quantityElementPage: res.perPage, // this line
});
} catch (e) {
console.error(e);
}
}
fetchData(value.currentPage, value.quantityElementPage); // this line
}, [value.currentPage, value.quantityElementPage]); // this line
const changePage = (argPage) => {
setValue((prev) => ({
...prev,
currentPage: argPage,
}));
};
const changeQuantityElementPage = (argElement) => { // this method
setValue((prev) => ({
...prev,
quantityElementPage: argElement,
}));
};
return (
<div>
<Table dataAttribute={value.listCategory} />
{[...Array(value.buttonsPagination)].map((item, index) => (
<button key={'listCategory' + index}
onClick={() => changePage(index + 1)}>{index + 1}
</button>
))}
//here I display button who choose quantity element:
{[...Array(value.buttonsQuantityElementPage)].map((item, index) => (
<button onClick={() => changeQuantityElementPage(index+2)}>quantity element - {index+2}
</button>
))}
</div>
);
};
apiCategory.js:
export const apiCategory = async (url, args, valuePage, valueElement) => { //add valueElement in argument
const getToken = localStorage.getItem('myToken');
const response = await fetch(`${apiUrl}${url}?page=${valuePage}&limit=${valueElement}`, { //add valueElement in param limit
...args,
headers: {
"Content-type": "application/json; charset=UTF-8 ",
"Authorization": `Bearer ${getToken}`,
"Accept": 'application/json',
...args.headers,
},
});
return response.json();
}
This is happening because your value.buttonsQuantityElementPage is undefined. When you call setValue in useEffect, you did not include the previous values. buttonsQuantityElementPage no longer exists on value.
Also, if the Array constructor Array() receives an undefined value, it will return an array with a single undefined value. i.e. [...Array(undefinedValue)] will yield [undefined]
There are two options for a fix.
Option 1. Update the setValue in your useEffect to include all previous values and then override the properties with new values.
setValue(prev => ({
...prev,
listCategory: res.data,
currentPage: res.page,
buttonsPagination: Math.ceil(res.total / res.perPage),
quantityElementPage: res.perPage,
}));
Option 2. Split out the buttonsQuantityElementPage into its own variable. As far as I can see in your code, buttonsQuantityElementPage does not change. If that is the case then use
const buttonsQuantityElementPage = 3
and in the return section
[...Array(buttonsQuantityElementPage)].map((item, index) => (
<button onClick={() => changeQuantityElementPage(index + 2)}>quantity element - {index + 2}
</button>
))

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

How to make polling on react-table

I'm trying to build a React-Table which can make polling to a remote server every second to fetch newest data. I just followed what the author did in the doc (https://react-table.js.org/#/story/server-side-data) and tried integrate the polling function (setInterval) in "componentDidMount" but it FAILED.
The error message shows that when running "requestData" under "componentDidMount", "filtered" is undefined, whose length is not accessible. How can I fix that? Thank you.
import React from 'react';
import _ from 'lodash'
import ReactTable from "react-table";
import 'react-table/react-table.css'
const requestData = (pageSize, page, sorted, filtered) => {
return fetch(
'http://127.0.0.1:5000/agent',
{ method: 'GET'}
).then( res => res.json()
).then( filteredData => {
if (filtered.length) {
filteredData = filtered.reduce((filteredSoFar, nextFilter) => {
return filteredSoFar.filter(row => {
return (row[nextFilter.id] + "").includes(nextFilter.value);
});
}, filteredData);
}
const sortedData = _.orderBy(
filteredData,
sorted.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity;
}
return typeof row[sort.id] === "string"
? row[sort.id].toLowerCase()
: row[sort.id];
};
}),
sorted.map(d => (d.desc ? "desc" : "asc"))
);
const res = {
rows: sortedData.slice(pageSize * page, pageSize * page + pageSize),
pages: Math.ceil(filteredData.length / pageSize)
};
return res;
});
};
class AgentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
pages: null,
// loading: true,
};
this.fetchData = this.fetchData.bind(this);
}
fetchData(state, instance) {
// this.setState({
// loading: true
// });
requestData(
state.pageSize,
state.page,
state.sorted,
state.filtered
).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
// loading: false,
})
})
}
componentDidMount() {
setInterval(
() => requestData(
this.state.pageSize,
this.state.page,
this.state.sorted,
this.state.filtered
).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
// loading: false,
})
}), 5000
);
}
render() {
const { data, pages, loading } = this.state;
return (
<div>
<ReactTable
columns={[
{
Header: "Agent ID",
accessor: "AGENTID"
},
{
Header: "Description",
accessor: "DESCRIPTION"
},
{
Header: "Domain",
accessor: "DOMAIN"
},
{
Header: "Register Time",
accessor: "REGTIME"
},
{
Header: "Status",
accessor: "STATUS"
},
]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
data={data}
pages={pages} // Display the total number of pages
loading={loading} // Display the loading overlay when we need it
onFetchData={this.fetchData} // Request new data when things change
filterable
defaultPageSize={20}
className="-striped -highlight"
/>
</div>
);
}
}
export default AgentTable;
First off, you need to understand what the onFetchData callback is.
Taken from the docs, onFetchData is:
This function is called at componentDidMount and any time sorting, pagination or filterting is changed in the table
So what you're trying to achieve here won't work the way tried to.
Seeing as fetching data from a remote server every second isn't one of the conditions that invoke the onFetchData callback, you should try a different approach.
I forked React-Table's Simple Table example and added timed data requests here, this should help you get started.

Resources