useInfiniteQuery customization - reactjs

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.

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 ?

Nextjs navigating around is too slow, SSR specifically

Obviously this question has been asked before but the answers fail to help me.
My problem is server side rendering takes way too long, Navigating from page to page takes like 2.5-4 and sometimes 10 seconds. All I do is 2 queries to the database using prisma and few simple necessary functions.
Im aware that images are a big factor in performance, but despite using a cdn and optimizing images to the maximum it still isn't enough.
My question is how will nextjs handle heavy requests and lots of data in a real big website if it can't handle my pathetic website?.
Please keep in mind that this is my first app using nextjs and im certain that im missing something out.
Here's the link to the site, navigate around and see for yourself, I've added a progress bar in hopes of making it less painful but still the old "smooth react navigation" isn't there.
https://elvito-property.netlify.app/
Link to the repo
https://github.com/VitoMedlej/elvito-property
And ofcourse here is the full code I use to fetch the data using getServerSideProps
`
const isCategoryValid = (categoryQuery : string) => {
let categories = ["apartment", "villa", "comercial", "land", "chalet"]
if (categories.includes(categoryQuery)) {
return categoryQuery
}
return undefined
}`
const isPurposeValid = (purposeQuery : string) => { if (purposeQuery === 'for-sale' || purposeQuery === 'for-rent') { return purposeQuery } return undefined }
`const GetTotalCount = async(type?: string, purpose?: string) => {
const prisma = new PrismaClient()
const totalCount = await prisma
.properties
.count({
where: {
type,
purpose
}
})
return totalCount || 0
}`
`export async function getServerSideProps({query} : any) {
const select = {
id: true,
type: true,
bathrooms: true,
rooms: true,
price: true,
propertySize: true,
images: true,
title: true,
location: true,
purpose: true,
currency: true,
description: true
}
const itemsPerPage = 9
const prisma = new PrismaClient()
const purpose = isPurposeValid(`${query.purpose}`)
const type = isCategoryValid(`${query.category}`)
try {
const currentPage = query.page || 0;
const totalCount = await GetTotalCount(type, purpose) || 0
const totalPages = Math.round(totalCount / itemsPerPage)
let skip = (currentPage * itemsPerPage) || undefined
if (currentPage > totalPages || currentPage < 0)
skip = 0
let data : any = await prisma
.properties
.findMany({
skip,
take: itemsPerPage,
where: {
purpose,
type
},
select
})
// just returning the first image because that's all I need, wish prisma provided
// an easier way to do this but oh well
data.forEach((item : any) => {
item.images
? item.images = item.images[0]
: ''
})
// just a way to deal with bigints
bigInt_To_Number(data)
return {
props: {
results: data,
totalCount
}
}
} catch (err) {
console.log('err 1.4: ', err);
return {props: {}}
} finally {
await prisma.$disconnect()
}
}
`
The only thing that I can suggest is to find a way to switch to get static props or get static paths
Also I hope this discussion can also help you to find the preferred solution for you
https://github.com/vercel/next.js/discussions/32243

state is not managed correctly in react functional component

I'm trying to work on a react page that has 3 states: View: 0, Edit and Add
the page state keeps track of current record id and the state page is in. Here are the rules.
record id can change only if page is in view mode
if page enters Edit/Add mode then unless user is out of that mode, record id must not change and the page state stays as it is
here is my code to achieve the same
interface IPageManager{
id: number,
mode: Modes,
grid: {page: number, size: number},
setId(id: number): void,
enterAddMode(): void,
enterEditMode(teamId: number): void,
enterViewMode(id?: number): void
debug(msg: string):void
}
type PageStateType = {
grid: {page: number, size: number},
id: number,
mode: Modes
}
function usePageManager(): IPageManager {
const [state, setState] = useState({ grid: {page: 1, size: 10}, id: 0, mode: Modes.View } as PageStateType)
const isEditing = () => state.mode === Modes.Add || state.mode === Modes.Edit
const change = (teamId?: number, mode: Modes= Modes.View)=>{
if(!isEditing())
setState(current=>produce(current, x=>{
if(teamId)
x.id = teamId
x.mode = mode
}))
}
const manager: IPageManager = {
get id() { return state.id }
, get mode() { return state.mode }
, get grid() { return state.grid }
, setId(id: number) {
setState(previous => produce(previous, x => { x.id = id }))
}
, enterAddMode() { change(undefined, Modes.Add) }
, enterEditMode(teamId: number) { change(teamId, Modes.Edit) }
, enterViewMode(id?: number){
if(!isEditing()){
setState(previous=>produce(previous, x=>{
x.mode = Modes.View
if(id)
x.id = id as number
}))
}
}
, debug(msg: string){
const mode = state.mode===Modes.View? 'View': state.mode===Modes.Edit? 'Edit':'Add'
trace(`${msg} - {id:${state.id}, mode:${mode}}`)
}
}
return manager
}
export function TeamsPage() {
const header = useMemo(() => <PageHeader title="Teams Management" />, [])
const manager = usePageManager()
manager.debug('page') // reflects correct state
const { data }= useQuery('teams', async () => await getTeams(manager.grid.page, manager.grid.size))
const view =(id: number)=>{
manager.debug(`before`) // manager state always at initial state
if(manager.mode===Modes.View) //always true since default is View
{
manager.setId(id)
manager.debug(`after`) // manager state always at initial state
}
}
const listing = useMemo(() => {
const { rows, count } = data ?? { rows: [], count: 0 } as TeamSummary
const t0 = (
<Listing
data={rows}
page={manager.grid.page}
size={manager.grid.size}
count={count}
onSelect={(arg) => view(arg.id) }
onAdd={() => manager.enterAddMode()}
onEdit={(arg) => manager.enterEditMode(arg.id)}
/>)
return t0
}, [data])
useEffect(() => { // if the data is updated, set the recordId to the first record in grid
const { rows, count } = data ?? { rows: [], count: 0 } as TeamSummary
if (rows && rows.length>0) {
const first = _.first(rows)
if (first)
manager.setId(first.id)
}
}, [data])
// it renders either an editor or a viewer, does not mantain any state on its own. depends on props only
const detail =<RecordBar id={manager.id} mode={manager.mode} getRecord={getTeam} emptyRecord={() => emptyTeamDetailResponse} />
return (
<Layout
summary={listing}
detail={detail}
header={header}
footer={<></>}
/>
)
}
In my opinion the page should work correctly but this is what I see in output
As you can see, after entering the Edit mode, page does change the current id despite the fact that if condition checks if the page is in view state or not before making any changes. To that if condition, state is always initial state even though in page it is reflected correctly.
is it some sort of closure problem or react issue or my coding is incorrect?
is it the correct way to manage the view i.e. by creating a custom hook that holds the state and returns the functions to operate on state?

fetchMore causes page to re-render unexpectedly

I have an infinite scroll list. I recently updated to the latest Apollo client and noticed infinite scroll no longer works.
Upon deeper investigation. I noticed when I call fetchmore with the incremented skip, it causes the entire page to re-render. Any ideas?
Query:
const {
data,
loading: queryLoading,
fetchMore,
error,
networkStatus
} = useQuery(SEARCH_PROFILES, {
variables: { ...searchParams, skip: skip.current },
fetchPolicy: "cache-first",
notifyOnNetworkStatusChange: true
});
FetchMore
const fetchData = () => {
ErrorHandler.setBreadcrumb("Fetch more profiles");
skip.current =
skip.current + parseInt(process.env.REACT_APP_SEARCHPROS_LIMIT);
const intLimit = parseInt(process.env.REACT_APP_SEARCHPROS_LIMIT);
if (hasMore.current) {
fetchMore({
variables: {
searchType,
long,
lat,
distance,
ageRange,
interestedIn,
skip: skip.current,
limit: intLimit,
isMobile: sessionStorage.getItem("isMobile")
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) {
hasMore.current = false;
return previousResult;
} else if (
fetchMoreResult.searchProfiles.profiles.length < intLimit
) {
hasMore.current = false;
}
const newData = produce(previousResult, (draftState) => {
if (draftState.searchProfiles.profiles) {
draftState.searchProfiles.profiles.push(
...fetchMoreResult.searchProfiles.profiles
);
} else {
draftState.searchProfiles.profiles =
fetchMoreResult.searchProfiles.profiles;
}
});
return newData;
}
});
}
};
Well, From your explanation, re-rendering is necessary since you're loading new content on while you scroll,
but for entire page not to be re-rendered is what we're concerned here... below tips might help.
extract the part of the page which needs to fetch data on scroll into a separate component, since it will be the only component which needs to be re-rendered
wrap your extracted component with React.memo() so it doesnt re-render when there is no change on data.
Make good use of life cycle hooks methods, they're the tools to manage on where or how to re-render

How to paginate data?

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 => (
....
....

Resources