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/163 update URL on search page #965

Merged
merged 19 commits into from
Sep 23, 2021

Conversation

ycanardeau
Copy link
Contributor

@ycanardeau ycanardeau commented Sep 13, 2021

Closes #163.

The first attempt

I tried to create two custom hooks:

  1. Update the URL whenever the state changes.
  2. Update the state whenever the URL changes.

It was difficult to implement this feature only by using custom hooks.

The second attempt

The most important thing in this implementation was to not update stores directly from the UI. Instead, always set new query params by calling a function. This idea was inspired by React's one-way data flow. See also Sync queryParameters with Redux state and react router for function components.

  1. Handle user input (onClick, onChange and etc.).
  2. Rewrite the URL by calling a function, which uses useNavigate from React Router internally.
  3. Components re-render.
  4. useEffect is called.
  5. Parse and validate query params by using qs and ajv.
  6. Update the store based on the parsed query params. This is done in a MobX action at once, not one by one.
  7. Check if search results should be cleared by comparing the current state with the previous one.
  8. Load search results.

However, there were some drawbacks with this implementation.

  1. It was slow because all child components were re-rendered whenever the URL (or the state) changes.
  2. I had to rewrite the URL by calling a function programmatically, rather than changing an observable in a store, which means we can't take advantage of MobX.

The third attempt

I was surprised that riipah already implemented this feature on the song rankings page by using ObservableUrlParamRouter. The third implementation is based on his idea, and basically does two things as I did in the first attempt:

  1. Update the URL whenever the state changes (fast).
  2. Update the state whenever the URL changes (slow).

For the first case, it's relatively fast because it only pushes changes to URL (not all child components will be re-rendered). For the second case, it's relatively slow because all child components (except for the ones wrapped with React.memo) will be re-rendered. I'd say this is how React Router (React Context) was designed. This implementation uses code from the first and second attempts as well.

I couldn't find a way to push changes to URL without re-rendering, but finally I found one. Thanks!

There are still some drawbacks with the current implementation.

  1. There are still some overheads when validating the route params (But I'd say ajv is fast enough).
  2. The query string is too long. We could omit falsy values from the query string.
  3. Artist filters, tag filters and etc. are reloaded every time the back/forward buttons are clicked. We should cache search results somehow.

How it works

There are three custom hooks: useStoreWithRouteParams, useStoreWithUpdateResults and useStoreWithPaging. These hooks are defined as below:

const useStoreWithRouteParams = <T>(store: IStoreWithRouteParams<T>): void;
const useStoreWithUpdateResults = <T extends Object>(store: IStoreWithUpdateResults<T>, onClearResults: (popState: boolean) => void): void;
const useStoreWithPaging = <T>(store: IStoreWithPaging<T>): void;

The useStoreWithRouteParams hook

The useStoreWithRouteParams hook updates a store that implements the IStoreWithRouteParams interface when a route changes, and vice versa. The IStoreWithRouteParams interface is defined as below:

interface IStoreWithRouteParams<T> {
  routeParams: T;
  popState: boolean;

  validateRouteParams: (data: any): data => T;
}

The popState property is set by the useStoreWithRouteParams hook to prevent adding the previous state to history.

The validateRouteParams function validates route params and should return true if and only if the passed data is valid. Validation happens every time location (not URL) changes, which means, when the page is first loaded, when the back/forward buttons on the browser are clicked, or when the page is navigated to a new location programmatically by using the useNavigate hook. For example, the <Link> component uses the useNavigate hook internally.

The routeParams property gets and sets the state of the store.

Reactions should be independent: Does your code rely on some other reaction having to run first? If that is the case, you probably either violated the first rule, or the new reaction you are about to create should be merged into the one it is depending upon. MobX does not guarantee the order in which reactions will be run.

Source: https://mobx.js.org/reactions.html

The useStoreWithUpdateResults hook

The useStoreWithUpdateResults updates search results whenever the routeParams property changes. The IStoreWithUpdateResult interface is defined as below:

interface IStoreWithUpdateResults<T> extends IStoreWithRouteParams<T> {
  clearResultsByQueryKeys: string[];

  updateResults: (clearResults: boolean) => void;
}

The useStoreWithUpdateResults hook determines if search results should be cleared by comparing the current and previous values. If that's the case, the onClearResults callback is called.

The useStoreWithPaging hook

The useStoreWithPaging hook is a helper hook that is composed of the useStoreWithUpdateResults and useStoreWithRouteParams hooks. The IStoreWithPaging interface is defined as below:

interface IStoreWithPaging<T> extends IStoreWithUpdateResults<T> {
  paging: ServerSidePagingStore;
}

How to generate schema

We use JSON schema to validate route params. Use typescript-json-schema project/directory/tsconfig.json TYPE to generate schema from a TypeScript type. For more information, see https://github.com/YousefED/typescript-json-schema.

@ycanardeau ycanardeau self-assigned this Sep 13, 2021
@ycanardeau ycanardeau merged commit 1511048 into future Sep 23, 2021
@ycanardeau ycanardeau deleted the feat/163-update-url-on-search-page branch September 23, 2021 12:00
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update URL on search page (client side routing?)
1 participant