Advertisement

#14 Remembering User Navigation Using A Route History

In this article we will start by introducing a new constant and a few new types to our store types. The constant will make it so that we will not have to use a magic string when using our state decorator when binding the account state to the corresponding field within the accounts view and the types will make it easier when defining the actions and mutations that operate on our state. After that is done we will create a new route state that will be responsible for maintaining a history of the navigations that the user preforms so that we can modify our approach to providing backward navigation functionality. Instead of using the position of the to and from routes within the routing tree we will just follow the history of route changes until we exhaust them when the user clicks on the back button.

Say no to magic strings

  • root
    • src
      • store
        • store-constants.ts

In the past couple of articles we have used our state decorator to bind an object contained within our vuex state to a field within our vue components. In order to tell the decorator which part of the store we are wanting access to we need to pass in a string. If you would only be accessing the state in a single location I guess passing in a string literal would not make much of a difference but here soon we will be accessing the same state from different locations. To make things a bit more robust we are going to start this article by adding another constant to our store for the only state we currently have (a).

store-constants.ts

...
export const STATE_ACCOUNTS = "accounts";
(a) Adding a constant to our store that we can use to access the accounts state.

More types

  • root
    • src
      • store
        • store-types.ts

Next up we are going to introduce a few new types to our store so that we can make it a bit easier to both create actions and mutations within our modules and in the case of actions make it easier to declare them within our components (b).

store-types.ts

import {
    ActionContext,
    ActionTree,
    MutationTree,
} from "vuex";
...
export type ActionFn<T> = (payload: T) => void;
export type StoreActionTree = ActionTree<IStoreState, IStoreState>;
export type StoreContext = ActionContext<IStoreState, IStoreState>;
export type StoreMutationTree = MutationTree<IStoreState>;
(b) Defining a few new types to make it easier to create actions and mutations and to use the actions.

Using the new types in our account module

  • root
    • src
      • store
        • account-module.ts

As we can see in (c), the new types that we have just created makes it a lot easier to create our actions and mutations.

account-module.ts

import {
    ActionContext, // <-- remove
    ActionTree, // <-- remove
    MutationTree, // <-- remove
    ...
} from "vuex";
...
import {
    ...
    StoreActionTree,
    StoreContext,
    StoreMutationTree,
} from "@/store/store-types";
...
type AccountContext = ActionContext<IStoreState, IStoreState>; // <-- remove
...
export const actions: StoreActionTree = {
    [ACTION_ADD_ACCOUNT](..., { commit }: StoreContext, ...) {
        ...
    },
    [ACTION_REMOVE_ACCOUNT](..., { commit }: StoreContext, ...) {
        ...
    },
};

export const mutations: StoreMutationTree = {
    ...
};
(c) Updating the accounts actions and mutations to take advantage of the new types we have defined.

Simplifying our accounts view

  • root
    • src
      • views
        • Accounts.vue

And of course we can also use these new types in our accounts view to make things a little nicer to use (d).

Accounts.vue

...
import {
    ...
    ActionFn,
    ...
    STATE_ACCOUNTS,
    ...
} from "@/store";
...
export default class Accounts extends Vue {
    ...
    @Action(ACTION_ADD_ACCOUNT) private readonly addAccount!: ActionFn<AddAccountPayload>;
    @Action(ACTION_REMOVE_ACCOUNT) private readonly removeAccount!: ActionFn<RemoveAccountPayload>;
    @State(STATE_ACCOUNTS) private readonly accountState!: IAccountState;
    ...
}
(d) Using our new types in the accounts view.

Adding route types

  • root
    • src
      • store
        • route-types.ts

Now that we have updated the code that we have previously added it is now time to add some more. Our goal is to update the behavior of our back button located within the app bar. Currently the route that is considered back is determined by the route tree that we build when creating our routes. This is definitely a workable approach but I would like to switch things up and use the back button as a true back button and have it point back to the route that the user just navigated away from. Our first step in achieving this goal is to add an interface and a few more types (e).

route-types.ts

import { Routes } from "@/components/routing";

export interface IRouteState {
    history: Routes[];
}

export type PushRoutePayload = Routes;
(e) Adding an interface to represent our route state and a couple of types that we can use to modify that state.

Might as well export them now

  • root
    • src
      • store
        • index.ts

There would not be much point in creating the interface and types that we just created if they were not going to be needed elsewhere. So as usual to make them easier to import we are going to export them from the index file (f).

index.ts

...
export * from "@/store/route-types";
...
(f) Exporting the route types from the index file.
Advertisement

The initial route state

  • root
    • src
      • store
        • route-initial-state.ts

Our initial route state is pretty simple to define (g).

route-initial-state.ts

import { IRouteState } from "@/store/route-types";

export const initialState: IRouteState = {
    history: [],
};
(g) Creating an initial route state that contains and empty history array.

Updating the store state

  • root
    • src
      • store
        • store-types.ts

Before we can create our actions and mutations for the route state we need to update the interface for our store (h).

store-types.ts

...
import { IRouteState } from "@/store/route-types";

export interface IStoreState {
    ...
    routes: IRouteState;
}
...
(h) Adding the route state interface to our store state interface.

More constants

  • root
    • src
      • store
        • store-constants.ts

Now that we are adding some additional state to our store, unless we want it to remain the same for all time, we are going to need to add some more actions and mutations and for that we are going to need some more constants (i).

store-constants.ts

...
export const ACTION_PUSH_ROUTE = "ACTION_PUSH_ROUTE";
...
export const MUTATION_PUSH_ROUTE = "MUTATION_PUSH_ROUTE";
...
(i) Adding more constants to support the addition of more actions and mutations.

Enter the new route module

  • root
    • src
      • store
        • route-module.ts

Next up is to add the actions and mutations that will allow us to modify our route state (j). The additions are fairly simple, for now we just need a way to add a route to the history when the user navigates to a new view. We will be using the history array as a last in first out stack.

route-module.ts

import {
    Store,
} from "vuex";

import {
    ACTION_PUSH_ROUTE,
    MUTATION_PUSH_ROUTE,
} from "@/store/store-constants";

import {
    IStoreState,
    StoreActionTree,
    StoreContext,
    StoreMutationTree,
} from "@/store/store-types";

import {
    PushRoutePayload,
} from "@/store/route-types";

export const actions: StoreActionTree = {
    [ACTION_PUSH_ROUTE](this: Store<IStoreState>, { commit }: StoreContext, route: PushRoutePayload) {
        commit(MUTATION_PUSH_ROUTE, route);
    },
};

export const mutations: StoreMutationTree = {
    [MUTATION_PUSH_ROUTE](state: IStoreState, payload: PushRoutePayload) {
        state.routes.history = [payload, ...state.routes.history];
    },
};
(j) Adding the push action and mutation that we can use to add routes to the history.

Life with the spread operator is much nicer

  • root
    • src
      • store
        • store.ts

Just as we did with our account initial state and its module we need to add the route initial state and the actions and mutations that we just created to our store (k).

store.ts

...
import { initialState as routeState } from "@/store/route-initial-state";
import {
    actions as routeActions,
    mutations as routeMutations,
} from "@/store/route-module";
...
const actions = {
    ...accountActions,
    ...routeActions,
};

const mutations = {
    ...accountMutations,
    ...routeMutations,
};

const state: IStoreState = {
    accounts: accountState,
    routes: routeState,
};
...
(k) Adding the route initial state and its actions and mutations to the store.

Pushing routes to our history

  • root
    • src
      • components
        • routing
          • TheRouterOutlet.vue

The last thing that we want to do for this article is use the action that we just created to add routes to our route history. We can take care of this very easily by just importing the action into the router view and using the from route entry's route property (l).

TheRouterOutlet.vue

...
import {
    ACTION_PUSH_ROUTE,
    Action,
    ActionFn,
    PushRoutePayload,
} from "@/store";
...
export default class TheRouterOutlet extends Vue {
    @Action(ACTION_PUSH_ROUTE) private readonly pushRoute!: ActionFn<PushRoutePayload>;
    ...
    private animate(route: Routes) {
        ...
        this.pushRoute(from.route);
        ...
    }
    ...
}
(l) Pushing the route property of the from route entry into our history array.
Exciton Interactive LLC
Advertisement