Router set options: method, title and state

Description

The browser history can be used with two different methods: push and replace. It can also store a title and an state object. More info at https://developer.mozilla.org/en-US/docs/Web/API/History

The param title seems to be ignored by most browsers, so I think we should not pass it down to the browser as it may lead to confusion. But we can store it.

User Stories

As a Frontity developer
I want to use the replace method (instead of push)
so that update the most recent entry on the history stack

As a Frontity developer
I want to populate the state and title of each history entry
so that I can recover that state in Frontity when the user moves around

Possible solution

Initial proposed solution (click to open)

Add a second parameter to actions.router.set for these options.

actions.router.set("/a-url");

actions.router.set("/new-url", {
  method: "push",
  title: "Some title",
  state: {
    someState: "that I want to save",
  },
});

actions.router.set("/other-url", {
  method: "replace",
  title: "Some other title",
  state: {
    otherState: "that I want to save",
  },
});

Save the title and state in state.router:

state.router.link === "/url";
state.router.title === "Some title";
state.router.state === {
  someState: "some state"
};

state.router.state feels a bit repetitive, but I guess thereā€™s nothing we can do.

The method "push" or "replace" is passed down to window.history.

Final implementation

Maybe we can use state.router.historyState instead of state.router.state? Does it make sense? It would provide more info than just calling it state, which besides being repetitive might also be confusing.

Hmmā€¦ thatā€™s a good point.

What if we end up adding a state.router.history array in the future? Would having:

  • state.router.historyState
  • state.router.history[0].state

be confusing?

Shit, naming is hard :sweat_smile:

We also have the same problem with title, which also belongs to the browser history. For consistency both should be named in the same manner.

What about:

state.router.browserHistory = {
  state: { ... },
  title: "...",
  url: "..."
};

Iā€™m adding url for consistency and Iā€™m calling it browserHistory to:

  • Make it more clear that this is related to the window.history.
  • Leave state.router.history for a possible array of URL changes controled by us.
1 Like

state.router.browserHistory sounds good to me.

Iā€™m a bit confused now, so I guess other users might be as well if they come to use this api. Is browserHistory editable or a read-only object? I mean, that as a user I might think that updating that part of the state is going to update the window.history.

EDIT:
It is more confusing to me that the browserHistory doesnā€™t exactly map to window.history.

One more thing: Iā€™d avoid exposing the title field in the frontity api. As itā€™s not used, Iā€™d just internally set title to empty string in order to prevent any future bugs, but thatā€™s it.

So my final API would look something like:

actions.router.set("/some-url", {
  method: "push",
  state: {
    someState: "useful data to save",
  },
});

state.router.link = "/current-url";
state.router.browserHistoryState = {
  someState: "useful data to save",
};
// or
state.router.windowHistoryState = {
  someState: "useful data to save",
};

Yes because that would match state.router.history, but I agree that it is confusing.


If we get rid of title, then using an object doesnā€™t make sense.

We will certainly end up introducing state.router.history sooner or later, so what about:

actions.router.set("/some-url", {
  method: "push",
  state: {
    someState: "useful data to save"
  }
});

state.router.link; // <- last link
state.router.method; // <- last method
state.router.state; // <- last state
state.router.history = [
  {
    link: "/some-url",
    method: "push",
    state: { someState: "useful data to save" }
  }
];

And we live with state.router.state :man_shrugging:

1 Like

Iā€™m fine with this. Should the history be implemented on this issue or just the state?

Only state and method.

I had doubts on what the state object stored in window.history should look like, because it was used to store the initial state.router.link and that link was used to update the state when popstate event was fired, i.e. when the user navigated back or forward though the history.

I decided to implement it in a way that what you get from window.history.state is exactly the same object you get from state.router.state, therefore removing the link field, and using window.location instead to update the app state when a popstate event fires.

You can see the current implementation here: https://github.com/frontity/frontity/pull/380

So, to summarize, the options were to implement a state object in window.history like the following:

{
  link: "/path",
  method: "push",
  state: {
    some: "state"
  }
}

or like:

{
  some: "state"
}

In addition to this, I wasnā€™t sure to what value state.router.method should store after a popstate event. The options where to store the original method in window.store.state and restore that value, or actually store pop and making it clear this way that we arrived to the current state because the user moved through history.

I decided that the most useful info would be to store pop, because knowing the original method used to create that state didnā€™t seem to provide any useful information. So the values that can be found in state.router.method would be 'push' | 'replace' | 'pop'.

I might be wrong, so please go through the PR and let me know what you think and if I should make any changes.

Maybe storing state.router.method makes no sense and we have to leave that for state.router.history.

What do you think? I canā€™t imagine a use case now for it.

I couldnā€™t either, I left it there because I thought you had something in mind :joy:

No, sorry. I guess I was still thinking about state.router.history where it does make sense :sweat_smile:

1 Like

Pull Request

Final implementation

Now the action actions.router.set accepts an optional options object as the second argument:

actions.router.set("/some-link");
actions.router.set("/some-link", {
  method: "push",
  state: {
    someState: "some state"
  }
});

The options currently available are:

The state object passed in the options is saved in state.router.state.

state.router = {
  link: "/some-link",
  state: {
    someState: "some state"
  }
};

The new method: "replace" option is useful to replace the current URL of the app, without creating a new entry in the history. It is particularly useful when you want to update the URL (or state object) of the current history entry in response to some user action that has changed the current URL but itā€™s not perceived that way, like an infinite scroll or a swipe.


@orballo feel free to add more things if I missed something.