infinity scroll in reactjs fixed data table - reactjs

I need to implement infinity scroll features for fixed data table.
I checked API doc, there is no events dispatched when user scroll to the end of the table. The only seemly helpful event is onScrollEnd, but that event gives me back scrollX and scrollY.
ScrollY is pretty huge, I don't know how to use this number to detect user is scrolling near the end.
Can anyone tell me how I can implement infinity scroll feature using fixed data table?
Thanks

It is a shame that such component does not have proper API for infinite scroll.
The solution is this (but it is ugly):
The onScrollEnd() returns you a scrollY value. You need to persist this value in component state, e.g. this.state.scroll. On each onScrollEnd, you have to check for equality:
this.state.scroll === nextState.scroll && nextState.scroll !== 0 // you dont want to load dada on scrollTop.
If this is true, then you can load additional data, update your store or what you have, and re-render the component.
BIG PROBLEM: the onScrollEnd() function is very very slow :( Maybe do debouncing/throttling?

Related

When swiping react native app - screens shows twice

Live example https://snack.expo.dev/su1-U5DZc
If I swipe screens with buttons - everything okay, but if I swipe with gesture - screens shows twice. Why so? Does this setWtf(state.index);
const onTouchStart = (e, state, context) => {
console.log('onTouchStart: ' + state.index);
setWtf(state.index);
};
make index stored somewhere by reference and then get updated?
Yes it was due to setWtf(state.index);. There is a reported bug of render issue when updating state in their official git page.
They are saying to downgrade react-native-swiper to version 1.5.5
or you could follow other solutions mentioned here : Update the state breaks the slides
Without more information, it's impossible to pinpoint the exact cause of the problem you're experiencing, although it's conceivable that the problem is connected to the way your code handles touch events.
Your code is probably adjusting the value of state.index depending on the gesture when you swipe between screens, and then refreshing the screen display based on the value of state.index. The screen display may update twice if state.index is being updated by reference because the value is being modified twice: once by the gesture and once by the setWtf function.
Using a different variable to hold the current screen index and only updating the value of state.index when the user interacts with the buttons is one potential fix for this problem. This should prevent the screen from being displayed more than once and ensure that the value of state.index is only modified once.
It's important to keep in mind that the setWtf function you specified in your query could not be connected to the problem you're having. Without more information, it's difficult to say for sure whether this function is being used to update another state in your application.

ReactQuery - useInfiniteQuery refetching issue

I have implemented infinite scroll on a project that is using React Query library.
So far so good. Everything works as expected using the useInfiniteScroll hook
One issue that I am encountering is the caching mechanism of this library.
If I query for a resource, ex: GET /posts/, will trigger a GET /posts/?page=0, scroll a bit down, get the next results, GET /posts/?page=1 which is totally fine. If I add search parameter to the page will do a GET /posts/?filter=someValue&page=0. All fine... but when I remove the filter from the search it will automatically do GET /posts/?page=0 & GET /posts/?page=1
A solution is to remove the query from the cache using the remove() method from the query itself. But this means that I have to manually do it for each query.
I would like to get a better solution that will cover all cases. I have a queryWrapper where I want to handle this.
I tried using the queryClient instances invalidateQueries and resetQueries but none seems to be able to remove it from the cache...
In the examples below I keep a ref for the params, if they are changed I can trigger the query to reset via useLayoutEffect hook. This part works as expected
invalidateQueries attempt
queryClient.invalidateQueries(
[queryKey, JSON.stringify(paramsRef.current)],
{
exact: true,
refetchActive: false,
refetchInactive: false
},
{ cancelRefetch: true }
);
resetQueries attempt
queryClient
.resetQueries([queryKey, JSON.stringify(paramsRef.current)], {
refetchPage: (page, index) => index === 0
})
I even tried the queryClient.clear() method which should remove all existing queries from the cache but still the page number somehow remains cached... I access the queryClient using useQueryClient hook. If I inspect it, I can see the queries inside.
Can someone please help me to sort this cache issue
Thanks!
but when I remove the filter from the search it will automatically do GET /posts/?page=0 & GET /posts/?page=1
This is the default react-query refetching behaviour: An infinite query only has one cache entry, with multiple pages. When a refetch happens, all currently visible pages will be refetched. This is documented here.
This is necessary for consistency, because if for example one entry was removed from page 1, and we wouldn't refetch page 2, the first entry on page 2 (old) would be equal to the last entry of page 1 (new).
Now the real question is, do you want a refetch or not when the filter changes? If not, then setting a staleTime would be the best solution. If you do want a refetch, refetching all pages is the safest option. Otherwise, you can try to remove all pages but the first one with queryClient.setQueryData when your query unmounts. react-query won't do that automatically because why would we delete data that the user has scrolled down to to see already.
Also note that for imperative refetches, like invalidateQueries, you have the option to pass a refetchPage function, where you can return a boolean for each page to indicate if it should be refetched or not. This is helpful if you only update one item and only want to refetch the page where this item resides. This is documented here.

ReactQuery: how to deal with conditional re-rendering when changing parameters of a query

I want to implement an infinite scroll in a component, but my back-end doesn't have a "current" endpoint which means that the actual version of infiniteQuery is out of the question (as far as I understand).
In my component I created a query using a stateful variable as a queryKey parameter:
useQuery([`posts_paginated`, { page }] {
// request to http://.../posts?_sort=createdAt&_order=desc&_page=${page}&_limit=10
}
My issue is that modifying the query key creates a new query instance triggering the isLoading boolean in the response object and the rendering value of the component depends an that:
return (IsLoading) ? <LoadingComponent /> : <ViewComponent />
This means that when the page variable is set, the ViewComponent is unmounted with the result that I lose the current position of the scrolling bar.
I tried invalidating the query instead so that only isFetching would trigger, but I think that since the callback closure for the query is already set the parameters values can't be changed that way (I couldn't get it to run with anything but the initial values).
I could stop rendering contitionally with isLoading or I could start managing my scroll bar position explicitely in some state, but I'm wondering if there is an obvious way to achieve what I need just using react-query.
pagination like this can be achieved by setting the keepPreviousData prop. it will keep the data of the previous query key returned as data when the key changes, along with a isPreviousData boolean flag so that you can maybe disable the next button or show a background loading indicator depending on it. The query will then always stay in success state, and the data will then transition directly to the new data when it arrives.
This is also described in the pagination docs
Also, I think you can get a "real" infinite query working even if the backend is paginated. getNextPageParam just needs something returned from the function that can then be "injected" into the queryFn. If you just want to return a count of the current pages in the cache and increment that by one each time - I don't think there is anything bad about this :)

Performance issues if MapComponent state is updated

I am not sure if this is an issue of react-leaflet-markercluster, react-leaflet, leaflet, react, or my code.
I have a map with several thousand markers and I am using react-leaflet-markercluster for marker clustering. If I need to update a global state of MapComponent, there is 1-3 seconds delay when this change is reflected.
I created a codesandox with 5000 markers and you can see there 2 use cases with performance issues:
1.) MapComponent is inside react-reflex element, that allows resizing panel and propagates new dimensions (width, height) to MapComponent. If width and height are changed, mapRef.invalidateSize() is called to update map dimensions. Resizing is extremely slow.
2.) If user clicks on Marker, global state selected is updated. It is a list of clicked marker ids. Map calls fitBounds method to focus on clicked marker and also marker icon is changed. There is around 1 second delay.
In my project, if I need to change a MapComponent state, it takes 2-3 seconds in dev mode when changes are reflected and it is just a single rerender of MapComponent and its elements (markers).
I took a look at Chrome performance profile and it seems like most time is spent in internal React methods.
It is possible to fix this by preventing rerendering using memo, which is similar to shouldComponentUpdate, but it makes whole code base too complicated. preferCanvas option doesn't change anything. I am wondering what is a good way to fix these issues.
The main problem I identified in your code is that you re-render the whole set of marker components. If you memoize the generation of those, you achieve a good performance boost; instead of running the .map in JSX, you can store all the components in a const; this way, the .map won't run on every render.
from this
...
<MarkerClusterGroup>
{markers.map((marker, i) => {
...
to something like this
const markerComponents = React.useMemo(() => {
return markers.map((marker) => {
return (
<MarkerContainer .../>
);
});
}, [markers, onMarkerClick]);
return (
<>
<MarkerClusterGroup>{markerComponents}</MarkerClusterGroup>
</>
);
The second refactor I tried is changing the way you select a marker. Instead of determining the selected prop from the selected array for each marker, I put a selected field on every marker object and update it when selecting a marker. Also, I add the position to the onClickHandler args to avoid looking for that in the markers array.
There are some other tweaks I don't explain here so please check my codesandbox version.
https://codesandbox.io/s/dreamy-andras-tfl67?file=/src/App.js

CanvasJS react redraws entire chart on data refresh

Whenever I update the datasets of my CanvasJS component, all lines get re-drawn (in an animated fashion) instead of the points simply being added to the end. How can I prevent this?
https://imgur.com/4dqUMwT (short clip of the re-drawing, can't inline this it seems.)
I'm currently calling setState every 5 seconds through an interval (which polls an API). In the top of my render method I perform some transformations to the array saved in state (e.g. .map((e) => { return { x: e.x, y: e.y }; });), and then I pass the resulting array to data of the CanvasJSChart.
Edit:
I have just found the following page: https://canvasjs.com/react-charts/dynamic-live-line-chart/. This however sounds like a terrible anti-pattern. I'm going to see about wrapping this in a component which returns false on shouldComponentUpdate, but I'm not sure if that will work. If anyone has a better idea, please let me know.

Resources