Created a simple react-admin application that pulls from a custom rest api. First page is displayed (default 10 per page. Click the Next button and nothing happens (still sends page=1 to the api). Click a second time and the page advances to page 2 (page=2), as expected. Click the third time and goes back to page 1 (page=1).
Then, if you click a fourth time, it goes page 2, then click again, goes to page 3, then click again, goes back to page 1. It continues with this pattern, each round, getting one page further before going back to page.
I'm able to get the correct results when calling the custom API outside of the react-admin app. I created a custom dataProvider to communicate with the API and maybe there's a problem with the getList function, but I can definitely see the page number passed into this function and it lines up with the odd results (page 1, then 1, 2, 1, then 1, 2, 3, 1, etc. The custom API expects the following query string for pagination: ?limit=10&page=1&orderBy=id&orderDir=ASC
The original react-admin tutorial returns 10 records. When I set the page limit to 5, it does seem to work OK (advances to page 2 on the first click of Next), but without more records, it's hard to test it completely. But my guess is it would work, since it is most certainly a problem with my code or the API (although, as I said, the API works outside the react app).
Here's my getList function:
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
const tokens = localStorage.getItem('tokens');
const objToken = JSON.parse(tokens);
options.user = {
authenticated: true,
token: `Bearer ${objToken.accessToken}`
};
return fetchUtils.fetchJson(url, options);
};
export default {
getList: (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const { q } = params.filter;
// Pagination and sort
let query = `limit=${perPage}&page=${page}&orderBy=${field}&orderDir=${order}`;
// Filter?
let useResource = '';
let useFilter = '';
if (q == null) {
// No filter: Use <resource>/ url
useResource = resource;
} else {
// Filter: Use append url with /find
useResource = `${resource}/find`;
useFilter = q;
console.log('useFilter: ', useFilter)
query += `&searchText=${useFilter}`;
}
const url = `${apiUrl}/${useResource}?${query}`;
return httpClient(url)
.then(({ json }) => ({
data: json.results,
total: json.totalRows,
}));
}, ...
Here's a screen shot of issue:
EDIT:
It looks like the correct query string is being sent but immediately after the first Next page click (page=2), page=1 is automatically sent again, returning to page one. This seems to be the case with subsequent Next clicks, as well. Thanks for helping out a newbie. But I just can't figure out why extra calls are being made returning to page 1.
Fixed in react-admin 3.4.3.
I updated using npm update and pagination works correctly.
I have exactly behavor with react 4.x.x
What i was expecting:
Going to next page when cliking on next, with react-admin 3.19 this is how my application worked
What happened instead:
when you click on the next page, pagination resets to 1 !
also, it does not take into account the pagination that I define.
on chrome default perPage is 5, even when i set it 10.
chrome_pagination_issue
on firefox default perPage=10, but i have the same issue
firefox_pagination_issue
Other information:
getList: (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
console.log(params);
const query = {
...fetchUtils.flattenObject(params.filter),
_sort: field,
_order: order,
_start: (page - 1) * perPage,
_end: page * perPage,
_resource:resource
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ headers, json }) => {
if (!json.hasOwnProperty('totalElements')) {
throw new Error(
"The numberOfElements property must be must be present in the Json response"
);
}
return {
data: json.content,
total: parseInt(json.totalElements,10)
};
});
}
#4658
Environment
React-admin version: 4.0.1 , 4.0.2
React version:18
Strict mode disabled
Browser: chrome, firefox
My backend is spring boot rest api
Related
I want to pass the watch time of a video the user has seen when user closes the page,reload the page or navigate to another page. I am using visibilityChange event for this. When i try to navigate to another page, the api call runs perfectly. But the data i am sending to the api is not updated correctly. I am going to provide the code and the output below so you can understand perfectly what my problem is.
useEffect(async () => {
const x = 0;
console.log("use effect is run number ::::", x + 1);
window.addEventListener("visibilitychange", sendViewTime);
return () => {
window.removeEventListener("visibilitychange", sendViewTime);
};
}, []);
I have added the event listener in the useEffect.
the sendViewTime method is the method i want to call on visibility change event. This Method is working perfectly but for some reason the params are not updated even though i have set their states in their relavant hooks.
const sendViewTime = async () => {
if (document.visibilityState === "hidden") {
console.log("the document is hidden");
const value = localStorage.getItem("jwt");
const initialValue = JSON.parse(value);
console.log("the send View Time is :::", played_time);
const params = {
video_id: video_id_url,
viewTime: played_time,
MET: MET_value,
weight: "",
};
console.log("params are :::", params);
await setEffort(params, initialValue).then((res) => {
console.log("set effort api response is ::: ", res);
});
} else {
console.log("the document is back online");
}
};
//This onProgress prop is from react Player. Here i am updating the state of video progress.
onProgress={(time) => {
console.log("the time is :::", time);
const time_1 = Math.round(time.playedSeconds);
const time_2 = JSON.stringify(time_1);
setPlayed_time(time_2);
console.log("the played time is :::", played_time);
}}
//OUTPUT
// the document is hidden.
// the send View Time is :::
//params are ::: {video_id: '23', viewTime: '', MET: undefined, weight: ''}
//set effort api response is ::: {status: 200, message: 'Success', data: {…}, time: '2.743 s'}
//the document is back online
Never mind guys. I found the solution. It seems that i have to pass played_time and met value as a prop to the useEffect.If you want to know how useEffect works please visit this link. In general is it better to use one or many useEffect hooks in a single component?.
So I have a table that I'm currently lazy loading, and I want to cache the data of the 5 most current pages. When a user clicks the forward or backward button, I will send the pageNumber that they are going to, and the direction they are going(prev or next).
In my getData function, I will check if the page they are going to exists in a cachedData array or not. If yes, then I will dispatch that data without making an api call. If no, I will see the direction they are going to. For example, if they are going 1 -> 5, then page 6, I will remove page 1 from the cachedData array, then push page 6 in, and if they are going backward 6 -> 1, I will remove page 6, cuz it's the farthest from current page, and push page 1 instead. Below are the implementations:
const cachedData = [];
async function getData({ payload }) {
const { pageNumber, ...currentPageInfo } = payload;
const cachedPage = cachedData.find(page => page.pageNumber === pageNumber);
if (!cachedPage) {
const response = await fetchData(currentPageInfo);
if (response) {
if (cachedData.length >= 5) {
if (currentPageInfo.direction === "next") cachedData.shift();
cachedData.pop();
}
const { pageInfo, dataList } = response.data;
cachedData.push({
dataList,
pageNumber,
pageInfo,
});
cachedData.sort((x, y) => x.pageNumber - y.pageNumber);
dispatch(getDataSuccess({ res: dataList, pageInfo }));
}
} else {
dispatch(
getDataSuccess({
res: cachedPage.dataList,
pageInfo: cachedPage.pageInfo,
})
);
}
}
Currently it's working, but I'm not sure I'm on the right track. Between the shifting, sorting and finding, the performance aren't very good. The logic is weird because we are using our own way of pagination, and not the usual skip and take approach. But the important thing is whether anything can be improved in this code. Thank you very much.
Currently working with NextJS, but struggling to make an indexing page of sorts. With the router, I'm trying to get the page number by doing this:
let router = useRouter()
let page = isNaN(router.query.page) ? 1 : parseInt(router.query.page);
So that when I go to page=1, page=2 etc, I get new sets of data.
The functionality for this is called in the same main component, and is a React Query function:
const {data, status} = useQuery(["keycaps", {manu: manuId}, {prof: profileId}, {col: colorId}, {stat: statusId}], getKeycaps)
And said function looks like this:
const getKeycaps = async(key) => {
const manuId = key.queryKey[1].manu
const profileIds = key.queryKey[2].prof.map(id => `profile.id=${id}`)
const colorIds = key.queryKey[3].col.map(id => `filter_colors.id=${id}`)
const statId = key.queryKey[4].stat
const profileQueryString = profileIds.join("&")
const colorQueryString = colorIds.join("&")
let urlParams = new URLSearchParams(document.location.search);
let page = urlParams.get("page") == null ? 1 : parseInt(urlParams.get("page"));
let start = (page * 10) - 10
const data = await axios(`
${process.env.REACT_APP_STRAPI_API}/keycaps?${manuId ? 'manufacturer.id=' + manuId + '&' : ''}${profileQueryString ? profileQueryString + '&' : ''}${colorQueryString ? colorQueryString + '&' : ''}_start=${start}&_limit=10`)
return data.data
}
When initially loading pages, like directly pasting the url of the index in (i.e. keycaps?page=2), it will get all the results all fine. However, if I start using navigational buttons like this:
<Link href={`/keycaps?page=${page - 1}`}> // next/link
<div className="w-32 rounded-lg cursor-pointer">
Prev
</div>
</Link>
<Link href={`/keycaps?page=${page + 1}`}>
<div className="w-32 rounded-lg cursor-pointer">
Next
</div>
</Link>
The whole thing starts to break down. Essentially, the page doesn't actually reload any data or results until the page is unfocused. So, if I press the Next button to go to the next page, it won't load the data until I do something like tab to a new window or check a different internet tab, and then when I come back, the data will all magically load within a second.
I've tried this with next build && next start too, and this produces the same results. I just want to get the page results when the next and prev page buttons are pressed, and in a way that doesn't require the user to switch tabs to get content.
I will note that I do have a getStaticProps on this as well. It does the following:
export async function getStaticProps() {
const allKeycaps = (await getAllKeycaps() || 'Error')
return {
props: { allKeycaps }
}
}
Which will call an api script, and said script does this:
async function fetchAPI(query, {variables} = {}) {
const res = await fetch(`${process.env.REACT_APP_STRAPI_API}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
console.log('json', json.data, variables)
return json.data
}
/* Keycap related grabs */
export async function getAllKeycaps() {
const data = await fetchAPI(
`
{
keycaps {
id
slug
name
published_at
updatedAt
profile {
id
name
}
manufacturer {
id
name
lead
}
status {
id
name
}
colors
filter_colors {
color
}
kits
designer
thumb {
formats
}
}
}
`
)
return data
}
Anyone know how to get this to work? To navigate between indexes like this? I've been trying to look for Next tutorials that use navigations like page 1, page 2 etc but all I can find is examples of blog articles with slugs, no index searches of any kind.
Thanks a lot in advance.
Answer found:
When setting data and status using useQuery
const curPage = router.query.page == null ? 1 : router.query.page
const {data, status} = useQuery(["keycaps", {manu: manuId}, {prof: profileId}, {col: colorId}, {stat: statusId}, {page: curPage}], getKeycaps)
And then, in getKeycaps
const page = key.queryKey[5].page
I guess the "urlParams" approach wasn't a good one? Or at least, not one that was updating quick enough. So passing through the router page number seems to work better.
How do I redirect to another route without a hard refresh?
I created an ionic / angular app for the first time. It's a survey and after the user answers a set of questions they are redirected to the result page.
My redirect to the result page looks like this:
import { Router } from '#angular/router'
private router: Router
this.router.navigate(['/result/' + this.current_survey.id]);
I only see my results after a hard refresh and I am not sure why.
The code which was the redirect happen is in my survey component and I redirect result component. In the ngOinit() auf result component I retrieve the saved data from web database but at this moment the data not exists.
The code block in survey component looks:
if (this.current_survey.finished) {
this.calculateResult().then((result) => {
this.current_survey.result = result;
this.storageService.update(this.current_survey, this.helperService.SURVEY_STORAGE).then(
(value) => {
this.storageService.removeKey(this.helperService.LAST_NOT_FINISHED_SURVEY);
this.router.navigate(['result', this.current_survey.id]);
});
});
}
The ngOinit in my result component looks:
// Check if survey is a new survey or should be load from previos surveys
let survey_id;
// Get id from route, if any id was given in url
this.route.paramMap.subscribe((params: ParamMap) => {
survey_id = params.get('id');
});
if (survey_id !== null) {
survey_id = parseInt(survey_id, 10);
}
this.showResults(survey_id);
The method which was called in ngOinit in my result component looks:
const key = this.helperService.SURVEY_STORAGE + '_' + survey_id;
// Preload all neccessary data
this.storageService.getDataByName(key).then((data) => {
if (data.length === 0) {
this.alertService.show('Error', '', 'Result not found :(');
} else {
this.survey = data.shift();
}
});
Perhaps you can provide more information. Where are the results of the survey being stored? If they're stored in a database, in the ngOnInit() method of the results page component you should be able to query the latest result. Another option might be that when you navigate to the results page, you've done so before the result of the survey had finished posting. You may be able to do something like this to ensure it has saved before you try to retrieve the value:
save(surveyResults, surveyId) {
this.someService.postResults(surveyResults)
.subscribe(() => this.navigate(['result', surveyId]))
}
I'm using this Gumroad-API npm package in order to fetch data from an external service (Gumroad). Unfortunately, it seems to use a .then() construct which can get a little unwieldy as you will find out below:
This is my meteor method:
Meteor.methods({
fetchGumroadData: () => {
const Gumroad = Meteor.npmRequire('gumroad-api');
let gumroad = new Gumroad({ token: Meteor.settings.gumroadAccessKey });
let before = "2099-12-04";
let after = "2014-12-04";
let page = 1;
let sales = [];
// Recursively defined to continue fetching the next page if it exists
let doThisAfterResponse = (response) => {
sales.push(response.sales);
if (response.next_page_url) {
page = page + 1;
gumroad.listSales(after, before, page).then(doThisAfterResponse);
} else {
let finalArray = R.unnest(sales);
console.log('result array length: ' + finalArray.length);
Meteor.call('insertSales', finalArray);
console.log('FINISHED');
}
}
gumroad.listSales(after, before, page).then(doThisAfterResponse); // run
}
});
Since the NPM package exposes the Gumorad API using something like this:
gumroad.listSales(after, before, page).then(callback)
I decided to do it recursively in order to grab all pages of data.
Let me try to re-cap what is happening here:
The journey starts on the last line of the code shown above.
The initial page is fetched, and doThisAfterResponse() is run for the first time.
We first dump the returned data into our sales array, and then we check if the response has given us a link to the next page (as an indication as to whether or not we're on the final page).
If so, we increment our page count and we make the API call again with the same function to handle the response again.
If not, this means we're at our final page. Now it's time to format the data using R.unnest and finally insert the finalArray of data into our database.
But a funny thing happens here. The entire execution halts at the Meteor.call() and I don't even get an error output to the server logs.
I even tried switching out the Meteor.call() for a simple: Sales.insert({text: 'testing'}) but the exact same behaviour is observed.
What I really need to do is to fetch the information and then store it into the database on the server. How can I make that happen?
EDIT: Please also see this other (much more simplified) SO question I made:
Calling a Meteor Method inside a Promise Callback [Halting w/o Error]
I ended up ditching the NPM package and writing my own API call. I could never figure out how to make my call inside the .then(). Here's the code:
fetchGumroadData: () => {
let sales = [];
const fetchData = (page = 1) => {
let options = {
data: {
access_token: Meteor.settings.gumroadAccessKey,
before: '2099-12-04',
after: '2014-12-04',
page: page,
}
};
HTTP.call('GET', 'https://api.gumroad.com/v2/sales', options, (err,res) => {
if (err) { // API call failed
console.log(err);
throw err;
} else { // API call successful
sales.push(...res.data.sales);
res.data.next_page_url ? fetchData(page + 1) : Meteor.call('addSalesFromAPI', sales);
}
});
};
fetchData(); // run the function to fetch data recursively
}