-
Notifications
You must be signed in to change notification settings - Fork 94
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
Inconsistent use of history leads to redundant rendering #130
Comments
Further investigation suggests a fundamental conceptual flaw with the way history is currently implemented. Roughly speaking, exhibit consists of a set of (mostly) independent components (views, facets) that each offer a view of an underlying collection of items. Each component has a state (sort selection, filter selection, etc.) that determines how it presents the collection.. Changes to the component's state (e.g., decision to sort a different way) cause a re-rendering of the component. Changes to the underlying collection of items also cause a re-rendering. A history mechanism has been implemented over this conceptual design; it aims to remember each change of state so the user can return to one. To support such history navigation it is necessary for each component to be able to import and export its state for use in the history. Because support for While apparently elegant, this approach suffers from some problems. At root, this is because the "current set of items" is not recorded as part of the state of any component. Instead, the current set of items is a function of the states of all the components (facets) taken together. When the user interacts with a facet and causes a change to that facet's state, the facet updates the current-items collection accordingly; this triggers an A lesser problem is performance. Because every facet change triggers an A more serious problem is semantic. There are cases where an But this raises a complicated issue: a change to a facet F, which is effected by invoking a In fact, the present implementation behaves differently: a change to pagination is handled directly without triggering a pagination event. On the one hand this is good; it avoids (some of) the redundant renderings. It also means we don't need to figure out how to handle a But this creates a semantic bug. Because the change to pagination is not exported as part of the current state, it won't be remembered. Instead, the current state reflects the pagination before it gets reset. So, returning to this history from the future can produce inconsistent results with what the user is seeing now. There's no apparent local fix for these problems. Because all re-rendering are handled through a More significantly, it's clear that we need to get pagination resets into the history so the history becomes semantically correct. Doing so is a challenge because the new state is stored before being passed to individual components' One option might be to "hold" the new state we generate and collect changes to it (from recursively generated pushState calls) until it "settles down", at which point we could store it. However, storing a new state is what generates the Another option would be to not generate the new state at all until it's ready: we could let individual components update directly as a result of Both of these options are essentially creating a second update pathway from the |
Pagination is a particularly illuminating case here. Consider the case where a user is moving "forward" and "backward" through the history. If the history involves a series of pagination changes, then the movement in history should cause that pagination to evolve in the expected way, e.g. "forward" moves forward one page in the pagination. But what should be done if the user "jumps" to a distant history entry which has a different collection but where the page is nonzero. By consistency with the first example, it would seem we should generate the page specified in the history. But as discussed in the previous comment, there is also great sense in going back to page 0 when items change, so as not to confused the user by placing them in the middle of a results list. Which of these two contradictory principles should be applied here? And how should it be implemented? Right now, in tabular view, an |
It appears that the standard design for views is a "reconstruct" method that gets called when they need to be regenerated based on changes to the underlying collection. This reconstruct method is called through two different pathways:
Right now, it seems that a change to the collection via a facet interaction causes both a direct change in the collection and a change to the history state, so the reconstruct method gets called twice for each collection change. This makes exhibit twice as slow, and also introduces some bugs due to races between the two rerenders. For example, in the tabular view, changing the collection resets the paginated page to zero (important if the number of items shrinks so you don't see an empty page) but importState does not. When importState runs first, you end up with a view showing no results because you're on the wrong page.
The text was updated successfully, but these errors were encountered: