Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rendering): always render with current state #5429

Merged
merged 33 commits into from
Jan 23, 2023

Conversation

Haroenv
Copy link
Contributor

@Haroenv Haroenv commented Jan 12, 2023

Summary

This change enables optimistic rendering, as discussed in RFC 82 and also #4403.

This doesn't (yet) implement reverting the state on error, as it is done in #5438.

Result

  • when the search/loading starts, a render happens as well
  • when we render, we use the current SearchParameters for _state in SearchResults
  • fix all edge cases that didn't correctly detect idle state before persisting state
  • introduce common unit/integration tests for optimistic ui in RefinementList, Pagination, Menu, HierarchicalMenu

FX-2196

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jan 12, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit a15a451:

Sandbox Source
InstantSearch.js Configuration
react-instantsearch-app Configuration
example-react-instantsearch-hooks-default-theme Configuration
example-vue-instantsearch-default-theme Configuration

examples/react-hooks/default-theme/src/App.tsx Outdated Show resolved Hide resolved
packages/instantsearch.js/src/lib/utils/render-args.ts Outdated Show resolved Hide resolved
// @MAJOR: use scheduleRender here
// For now, widgets don't expect to be rendered at the start of `loading`,
// so it would be a breaking change to add an extra render. We don't have
// these guarantees about the render event, thus emitting it once more
// isn't a breaking change.
this.emit('render');
this.scheduleRender(false);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is part B of the implementation

Comment on lines -203 to +204
"disjunctiveFacets": [
"brand",
],
"disjunctiveFacetsRefinements": {
"brand": [
"Apple",
],
},
"disjunctiveFacets": [],
"disjunctiveFacetsRefinements": {},
Copy link
Contributor Author

@Haroenv Haroenv Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are now always rendering with the current state. The state from the results is wrong (merged) and thus useless. This PR changes that.

packages/instantsearch.js/src/lib/utils/render-args.ts Outdated Show resolved Hide resolved
seems that react may skip the render if `loading` is visible <1 frame
Comment on lines 232 to 242
getResults() {
return derivedHelper && derivedHelper.lastResults;
if (!derivedHelper) return null;
if (!derivedHelper.lastResults) return null;

// To make the UI optimistic, we will always render using the current state,
// but the previous results. This means a change will be visible immediately,
// regardless of the status of the network request.
derivedHelper.lastResults._state = helper!.state;

return derivedHelper.lastResults;
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is part A of the implementation

@Haroenv Haroenv changed the title WIP: optimistic rendering feat(rendering): always render with current state Jan 17, 2023
@Haroenv Haroenv force-pushed the feat/optimistic-rendering branch from b0e8d6a to 0833320 Compare January 17, 2023 18:44
Comment on lines 546 to 554
expect(searchClient.search).toHaveBeenCalledTimes(0);
expect(appleRefinement).not.toBeChecked();

userEvent.click(appleRefinement);

await waitFor(() => {
expect(appleRefinement).toBeChecked();
expect(searchClient.search).toHaveBeenCalledTimes(1);
// possible bug in testing-library, this is checked in real life in the same situation
// expect(appleRefinement).toBeChecked();

This comment was marked as outdated.

Copy link
Contributor Author

@Haroenv Haroenv Jan 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was never correctly passing:

  • searchClient wasn't correct (returned no facets on result)
  • and behaviour it pretended to fix was never fixed (if result._state said something was refined, it would show as refined, but first click would only set it to refined again, second click would toggle again, because local search parameters are used for refine)

And the behaviour changed in this PR. It's sufficiently covered under the expanded "renders refinements from local widget state" test now.

@Haroenv Haroenv marked this pull request as ready for review January 18, 2023 10:37
Copy link
Member

@dhayab dhayab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

packages/instantsearch.js/src/widgets/index/index.ts Outdated Show resolved Hide resolved
Co-authored-by: Dhaya <154633+dhayab@users.noreply.github.com>
Copy link
Member

@sarahdayan sarahdayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to your existing tests, I think each flavor should have the same suite of user-oriented tests (testing the UI, not checking what methods are called) where we slow down the search request, click a refinement, and assess whether the checkbox is checked or not (before this PR it should not, with this PR it should).

Ideally, we do this for every impacted widget (all refinement widgets, pagination, etc.) This could be in a optimistic-ui test suite.

Later on, when we do a common test suite as in Predict, we could factorize that into one.

@Haroenv Haroenv requested review from dhayab and sarahdayan January 20, 2023 10:47
ariaLabel={`${pageNumber + 1}`}
ariaLabel={`Page ${pageNumber + 1}`}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other aria-labels aren't configurable, so changing this one to add text isn't a breaking change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can those changes be easily moved into their own PR? It adds a bit of noise and this one is already quite large.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests would fail or need to be skipped between the tests. Happy to revert in this PR if needed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's right, I saw you use those new aria label strings in tests. It's fine for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's added because they all three used different aria-label before this PR, so I didn't have a way to target the right items

@@ -88,6 +88,7 @@
<a
:class="suit('link')"
:href="state.createURL(page)"
:aria-label="`Page ${page + 1}`"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other aria-labels aren't configurable, so adding this one to add text isn't a breaking change

Comment on lines -441 to -442
// Fixes https://github.com/algolia/react-instantsearch/issues/3530
test('renders initial refinement and allows to refine them', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mentioned in a different comment too, but this test wasn't actually asserting what it was meant to (toBeChecked at the bottom should be .not.toBeChecked if we actually fixed the issue, but we never did). The similar assertion about it being interactive is added to the other test earlier.

Comment on lines +235 to +240
// To make the UI optimistic, we patch the state to display to the current
// one instead of the one associated with the latest results.
// This means user-driven UI changes (e.g., checked checkbox) are reflected
// immediately instead of waiting for Algolia to respond, regardless of
// the status of the network request.
derivedHelper.lastResults._state = helper!.state;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

root of the implementation

Copy link
Member

@dhayab dhayab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great set up for the common test suite!

ariaLabel={`${pageNumber + 1}`}
ariaLabel={`Page ${pageNumber + 1}`}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can those changes be easily moved into their own PR? It adds a bit of noise and this one is already quite large.

tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
// wait for initial results
await act(async () => {
await wait(margin + delay);
await wait(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain or comment why we wait a second time after most of the interactions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand, but I believe it's related to jest timers being quite strict in ordering. If there's no two timers waited for, a timer added during the initial wait will still be triggered after.

Copy link
Member

@sarahdayan sarahdayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments for the hierarchical-menu common suite apply to others when relevant.

tests/common-unit/hierarchical-menu/index.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/index.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
tests/common-unit/package.json Outdated Show resolved Hide resolved
@Haroenv Haroenv requested a review from sarahdayan January 20, 2023 15:45
Copy link
Member

@aymeric-giraudet aymeric-giraudet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice PR ! Great job on abstracting the common test suites :D

helper update caused a tiny bit increase, and React InstantSearch connectors was sized tightly
Copy link
Member

@sarahdayan sarahdayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gorgeous work on this topic, and I'm excited we have a common test suite now. Can't wait to move most our tests there!

tests/common/widgets/hierarchical-menu/optimistic-ui.ts Outdated Show resolved Hide resolved
@Haroenv Haroenv enabled auto-merge (squash) January 23, 2023 15:46
Copy link
Contributor

@FabienMotte FabienMotte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work on the common test suite 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants