hypothesis / lms

LTI app for integrating with learning management systems
BSD 2-Clause "Simplified" License
46 stars 14 forks source link

Dashboard smoother routes #6342

Closed acelaya closed 3 months ago

acelaya commented 3 months ago

This PR introduces a mechanism to load the data needed by a new section just before transitioning to it, allowing to keep the previous section rendered until the loading has finished.

This also provides an alternative approach to handle the initial loading, so it might replace https://github.com/hypothesis/lms/pull/6287

How it works

In order to achieve this, every component which represents a "page" and is therefore linked to a route is now expected to fulfill a module contract, exporting two symbols:

  1. A loader function, which returns a promise resolving the data needed by the component.
  2. A default export, which is the component to render, with a single loaderResult prop where the result of resolving the loader will be passed.

To orchestrate everything, we introduce a new component, which replaces the standard <Switch /> + <Route /> router components, and uses instances of useRoute to determine the matching route.

Once we know the matching route, we use a dynamic import(...) to load the corresponding module, call its loader, and once resolved, mount the corresponding component.

This also allow us to have three states.

  1. At first there's no component loaded at all. This is the initial loading state, where we could show a global loader or splash screen. This is why I mentioned this PR could replace https://github.com/hypothesis/lms/pull/6287
  2. When a component has already been loaded, we show it normally.
  3. When transitioning between two routes, we keep showing previous component, with an optional subtle loading indicator of some kind, and once the new loader has resolved, we replace the rendered component.

References

This approach is inspired by how some react full-stack frameworks resolve data loading. In their case data is loaded in the server and then components are hydrated with the result, but conceptually it's similar to this.

Considerations and next steps

acelaya commented 3 months ago

I created another PR based on this one, where the route matching logic is extracted to its own hook, and the map of route -> module could be passed as a prop: https://github.com/hypothesis/lms/pull/6357

acelaya commented 3 months ago

I just noticed the usage of dynamic import(...) statements causes rollup to generate individual chunk files for those modules. I guess this should be ok, but I'm checking if it's somehow possible to incorporate them in the same bundle file.

EDIT: the output.inlineDynamicImports rollup option would solve this if we wish to bundle everything in a single file.

acelaya commented 3 months ago

Closing for now, until we come back to this.