Open jpepper-wistia opened 1 year ago
Update:
I was able to resolve the 2nd bug where page number would not reset when filters change by manually specifying page={0}
as part of the Configure
parent element.
const Foo = () => {
return (
<InstantSearch {...props...}>
<Configure
filters={someFilters}
page={0} // this fixed `useInfiniteHits()` for me resetting the page number correctly when filters change
/>
{children}
</InstantSearch>
)
}
I am still running into a similar issue as https://github.com/algolia/instantsearch/issues/5263 when trying to refresh the data in useInfiniteHits
Regarding the issue you have posted an update about, Configure doesn't automatically reset the page, however the refine
functions from useMenu/useRefinementList do reset the page. This is because Configure is aimed at parameters that aren't visible to the user, and thus shouldn't reset any other parameters.
Your other issue is already tracked in #5263, we indeed don't have a solution for this today, as the behaviour isn't completely clear. InfiniteHits keeps track internally of all the "seen" pages. If you refresh, should the page become 0 as well, or should you stay at page N and clear the previous pages? Today only the "last seen page" gets an update, as refresh will only update that one.
What's the exact behaviour you'd expect for InfiniteHits?
Thanks for the response @Haroenv !
Regarding expected behavior for InfiniteHits
, we are using it with an "infinite scroll" UX pattern (not pagination) which should help avoid some of that complexity you listed for our purposes.
With infinite scroll in mind, we just need a way to "reset" the full state useInfiniteHits()
hook is using so that it can:
We believe this should allow us to correctly show filtered results depending on what data update actions a user performed.
Example Scenario A:
Example Scenario B:
Example Scenario C:
Our intended approach
useInfiniteHits()
for infinite scroll behavior and fetch additional pagesuseInfiniteHits()
somehow (where we are stuck)What we've tried
useInstantSearch().refresh()
hits
cache
with useInfiniteHits
as well as refresh()
from useInstantSearch
function useMemoryCache() {
return useMemo(() => {
let cachedHits;
let cachedState;
return {
read({ state }) {
return isEqual(cachedState, getStateWithoutPage(state)) ? cachedHits : null;
},
write({ state, hits }) {
cachedState = getStateWithoutPage(state);
cachedHits = hits;
},
clear() {
cachedHits = undefined;
cachedState = undefined;
},
};
}, []);
}
/// consuming code const Foo = () => { const cache = useMemoryCache(); const data = useInfiniteHits( { cache }); const { refresh } = useInstantSearch();
return ( <Bar onUserUpdate={() => { // NOTE: Also tried reversing these cache.clear(); refresh(); }) /> ) }
* A combination of a custom `cache` with `useInfiniteHits` as well as `setUiState()` from `useInstantSearch`
* As suggested as part of this comment https://github.com/algolia/instantsearch/issues/5263#issuecomment-1363062010
* This seems to run into race conditions as the behavior is unpredictable
E.g.
```tsx
function useMemoryCache() {
return useMemo(() => {
let cachedHits;
let cachedState;
return {
read({ state }) {
return isEqual(cachedState, getStateWithoutPage(state)) ? cachedHits : null;
},
write({ state, hits }) {
cachedState = getStateWithoutPage(state);
cachedHits = hits;
},
clear() {
cachedHits = undefined;
cachedState = undefined;
},
};
}, []);
}
const INDEX_NAME = 'SomeIndex';
/// consuming code
const Foo = () => {
const cache = useMemoryCache();
const data = useInfiniteHits( { cache });
const { setUiState } = useInstantSearch();
return (
<Bar
onUserUpdate={() => {
// NOTE: Also tried reversing these
setUiState((state) => {
state[INDEX_NAME].page = 0;
state[INDEX_NAME].configure.page = 0;
return state;
});
cache.clear();
})
/>
)
}
Do you have any suggestions on how to manually force "reset" useInfiniteHits
to a default state (page 0 and fresh results for that page)?
This is a major blocker for us as it prevents supporting user update/delete actions from views using Algolia results that require an infinite scroll
.
I believe the behaviour you're described may be fixed by combining setUiState
(or setIndexUiState
) combined with refresh
as well as cache.clear
, for good measure
I'd recommend trying this:
function createMemoryCache() {
let cachedHits;
let cachedState;
return {
read({ state }) {
return isEqual(cachedState, getStateWithoutPage(state)) ? cachedHits : null;
},
write({ state, hits }) {
cachedState = getStateWithoutPage(state);
cachedHits = hits;
},
clear() {
cachedHits = undefined;
cachedState = undefined;
},
};
}
}
const INDEX_NAME = 'SomeIndex';
/// consuming code
const cache = createMemoryCache();
const Foo = () => {
const data = useInfiniteHits( { cache });
const { setUiState, refresh } = useInstantSearch();
return (
<Bar
onUserUpdate={() => {
setUiState((state) => {
state[INDEX_NAME].page = 0;
state[INDEX_NAME].configure.page = 0;
return state;
});
cache.clear();
refresh();
})
/>
)
}
Note that you'll need to be careful to pass the memory cache to _every instance of useInfiniteHits
/InfiniteHits
__ you have on the page.
@Haroenv Thanks for the suggestion.
I attempted that approach and I'm seeing some unpredictable behavior with that solution.
Observed behavior:
I added some console logs to see whats going on:
function createMemoryCache() {
console.log('\t\t\t\t-CREATE MEMORY CACHE---');
let cachedHits;
let cachedState;
return {
read({ state }) {
console.log('\t\t\t-MEMORY CACHE: READ-state', cachedState);
console.log('\t\t\t-MEMORY CACHE: READ-cachedHits', cachedHits);
return isEqual(cachedState, getStateWithoutPage(state)) ? cachedHits : null;
},
write({ state, hits }) {
console.log('\t\t\t-MEMORY CACHE: WRITE-state', state);
console.log('\t\t\t-MEMORY CACHE: WRITE-cachedHits', hits);
cachedState = getStateWithoutPage(state);
cachedHits = hits;
},
clear() {
console.log('\t\t\t-MEMORY CACHE: CLEAR');
cachedHits = undefined;
cachedState = undefined;
},
};
}
onUserUpdate
callback example above is executedHere's what its showing in the console:
From debugging the createMemoryCache
here's what I've observed
cache.clear
is called, cachedHits
and cachedState
are undefined
as expectedwrite
function after the clear
is being called in the following state:
cachedHits
is undefined
as expected due to cache.clear
being calledcachedState
is undefined
as expected due to cache.clear
being calledhits
param of write
function contains the updated page 0
set of results (as expected)state
param of write
function is unpredictable
state
with page=2
listed (this is when stale data seems to occur and page 1 is skipped and cannot be scrolled to)
page=1
which causes stale data to leak through and be cached as wellpage=0
listed (this allows us to get into a "fresh" state as desired)Any thoughts on how to resolve the unpredictable behavior with the state
param thats being sent to the cache.write
function?
Follow up:
It looks like that unpredictable stale state is coming from connectInfiniteHits
-> getRenderedState
-> renderOptions
param has the state
referencing an older page when then gets rewritten to the cache.write
after its cleared.
Maybe it's some kind of race condition / sequencing of events?
Do you have this exact scenario in a sandbox (or even better a test) so we can look into what the solution would be?
As a workaround before this is fixed you could take isCleared
in account in the cache:
function createMemoryCache() {
console.log('\t\t\t\t-CREATE MEMORY CACHE---');
let cachedHits;
let cachedState;
let isCleared;
return {
read({ state }) {
if (isCleared) return null;
console.log('\t\t\t-MEMORY CACHE: READ-state', cachedState);
console.log('\t\t\t-MEMORY CACHE: READ-cachedHits', cachedHits);
return isEqual(cachedState, getStateWithoutPage(state)) ? cachedHits : null;
},
write({ state, hits }) {
console.log('\t\t\t-MEMORY CACHE: WRITE-state', state);
console.log('\t\t\t-MEMORY CACHE: WRITE-cachedHits', hits);
cachedState = getStateWithoutPage(state);
cachedHits = hits;
isCleared = false;
},
clear() {
console.log('\t\t\t-MEMORY CACHE: CLEAR');
cachedHits = undefined;
cachedState = undefined;
isCleared = true;
},
};
}
Hello @Haroenv, thanks for following up.
I tried the solution above, but we did not have success with adding the isCleared
logic to the cache either.
We do not have this scenario setup in a sandbox currently to share; however, I can add this as a note on our team to follow up on if its required to investigate a solution.
the useInfiniteHits hooks from "react-instantsearch-hooks-web" seems to have the right behaviour while "react-instantsearch-hooks" used in react native doesnt. Any solution on this?
@sbkl, there's no difference in behaviour between React InstantSearch Hooks Web and React InstantSearch Hooks with relation to this. Do you have a sandbox or other code sample to clarify what doesn't work for you? Maybe in a new discussion
🐛 Current behavior
Intended use cases
I want to use useInfiniteHits() and have it correctly reset its data when:
Problems
Refreshing Data with
useInfiniteHits()
When calling
refresh
fromuseInstantSearch
to hard refresh data from server after user update action:useInfiniteHits().hits
is not updated and retains stale data (from all local pages that were scrolled)useInfiniteHits().currentHits
is updated, but only reflects the latest page visited (e.g. page 2 instead of 0)useInfiniteHits().results.hits
is updated, but only reflects the latest page visited (e.g. page 2 instead of 0)Expected Behavior:
hits / currentHits
useInfiniteHits().hits
is updated and/or reset to first page of resultsRESOLVED: Changing Filters does not reset
useInfiniteHits()
current page numberWhen updating the selected filters object for the InstantSearch / Configure element's
filters
prop,useInfiniteHits
does not reset its state correctly:useInfiniteHits().results.page
retains the previous page number beforefilters
were changeduseInfiniteHits().hits
therefore shows results for a page that may not exist with the updatedfilters
NOTE: This was observed when providing an "empty" search string (in order to only leverage filters without text based search)
Expected Behavior:
filters
prop in aConfigure
element thats a child ofInstantSearch
will reset the page number for components using theuseInfiniteHits()
hook🔍 Steps to reproduce
Use a custom search client
E.g.
Use
useInfiniteHits
hookLive reproduction
none (see code snippet above)
💭 Expected behavior
Manually "refreshing" data
useInfiniteHits()
hook should not have conflicting and out of sync data betweenhits,
result.hitsand
currentHits`useInfiniteHits()
hook should refresh or reset its data correctly whenuseInstantSearch().refresh()
is ran, or provide another mechanism to "reset" the dataFetching updates results with changed filters and an empty search string
useInfiniteHits()
hook should not persist the previous page numbers when theConfigure.filters
property is updated.Package version
react-instantsearch-hooks 6.32.1
Operating system
macOs
Browser
Chrome
Code of Conduct