Categories
Vue 3

Vue Router 4–Named Routes and Multiple router-views

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Named Routes

We can name our routes to identify our route with a name instead of the path.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">Foo</router-link>
        <router-link :to="{ name: 'bar', params: { id: '123' }}"
          >Bar</router-link
        >
      </p>
      <p>{{ $router.currentRoute.value.params.id }}</p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };

      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        { path: "/foo", component: Foo },
        { path: "/bar/:id", component: Bar, name: "bar" }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to create a route bar that takes an :id URL parameter.

It also has a name property with the route name.

Then we have a router-link that has the to prop with an object.

The object has the route name as the value of the name property.

params has the route parameters that the route takes.

Named Views

We can have named views with Vue Router.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
      <router-view name="a"></router-view>
      <router-view name="b"></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const Baz = {
        template: "<div>baz</div>"
      };
      const routes = [
        {
          path: "/",
          components: {
            default: Foo,
            a: Bar,
            b: Baz
          }
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have an object in the routes array.

It has the path property with the URL path.

The components property has an object with the property names being the name prop values of the router-view s.

default is for a router view with no name.

In the template, we have the router-view s to show the route content.

Then we see:

foo
bar
baz

displayed.

Nested Named Views

We can also have named views in child routes.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
    </div>
    <script>
      const Parent = {
        template: `<div>
          parent
          <router-view></router-view>
          <router-view name="a"></router-view>
          <router-view name="b"></router-view>
        </div>`
      };

      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const Baz = {
        template: "<div>baz</div>"
      };
      const routes = [
        {
          path: "/parent",
          component: Parent,
          children: [
            {
              path: "child",
              components: {
                default: Foo,
                a: Bar,
                b: Baz
              }
            }
          ]
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the Parent route with the multiple router-view s.

The root template has one router-view .

Then the Parent component has the child router-view s.

In the routes array, we have an object with the /parent route that maps to the Parent component.

And we have a child route with the components for the router-view s.

Then we see:

parent
foo
bar
baz

displayed when we go to /parent/child .

Conclusion

We can have multiple router-view components with Vue Router 4.

Also, we can name our routes so we can use that for navigation.

Categories
Express Nodejs Vue 3

Create a Todo App with Vue 3, Express and Sematext

Vue 3 is the up and coming version of the popular Vue front end framework.

We can pair that we the back end of our choice to create an app that we want to create.

In this article, we’ll create a Vue 3 front end that’s paired with an Express back end that uses Sematext for logging.

Get Started

We can start by creating our scaffold.

First, we create a project folder with a backend and frontend folders inside.

Then we can go into our backend folder and create our Express app.

We can use the Express generator package to make this easy.

To run it, we run:

npx express-generator

in our backend folder to add the files.

Then we run:

npm i

to install the packages.

Next, we create our Vue 3 front end project.

To do that, we go into the project folder root and run:

npm init vite-app frontend

This will create the project files in the frontend folder and install the required packages.

Backend

Now we have the scaffolding for both apps, we can work on the back end app.

We install a few more packages that we’ll need for our back end.

To do that we run:

npm i cors dotenv sematext-agent-express sqlite3

cors is a package to let us communicate between front end and back end regardless of the domain they’re in.

dotenv lets us read the environment variables.

sematext-agent-express is the Sematext package for Express apps.

sqlite3 lets us save data to a SQLite database.

Next, we create a todo.js file in the routes folder.

And then in app.js, we change the existing code to:

require('dotenv').config()
const { stHttpLoggerMiddleware } = require('sematext-agent-express')
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require('cors');

var indexRouter = require('./routes/index');
var todosRouter = require('./routes/todo');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(cors())

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(stHttpLoggerMiddleware)

app.use('/', indexRouter);
app.use('/todos', todosRouter);



// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

We added the todosRouter middleware to let us use the todos route file.

And we have:

app.use('/todos', todosRouter);

to use the todosRouter file.

Also, we have:

require('dotenv').config()

to read the .env file which we’ll need for reading the API token for Sematext.

We add the Sematext Express middleware into our app with:

app.use(stHttpLoggerMiddleware)

so we can use its logging capabilities in our app.

We also have:

var cors = require('cors');

to let us do cross-domain communication.

Next, we go to todo.js and replace the existing code with:

var express = require('express');
var sqlite3 = require('sqlite3').verbose();
const { stLogger } = require('sematext-agent-express')

var db = new sqlite3.Database('./todos.db');
var router = express.Router();

router.get('/', (req, res) => {
  db.serialize(() => {
    db.all("SELECT * FROM todos", (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

router.post('/', (req, res) => {
  const { name } = req.body;

  db.serialize(() => {
    db.run("INSERT INTO todos (name) VALUES (?)", name, (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

router.put('/:id', (req, res) => {
  const { id } = req.params;
  const { name } = req.body;

  db.serialize(() => {
    db.run("UPDATE todos SET name = ? WHERE id = ?", name, id, (err, result) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(result);
      res.json(result);
    });
  });
});

router.delete('/:id', (req, res) => {
  const { id } = req.params;
  db.serialize(() => {
    db.run("DELETE FROM todos WHERE id = ?", id, (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

module.exports = router;

We add the routes with the router methods.

We use the stLogger object from the sematext-agent-express package to let us do the logging.

In each route, we have the stLogger.error method to log errors.

And we have stLogger.info to log other information like the database results.

Each time the route middleware runs, we’ll see something logged.

If there’s an error we return that as the response back to the client with the if statements.

We call db.serialize to run database queries in sequence.

And we use db.run to run INSERT, UPDATE, DELETE statements.

To run SELECT queries, we run db.all to get all the rows.

We use parameterized queries so that values are escaped before we run the queries.

req.params gets the request parameters from the URL.

req.body gets the request body.

We have:

var db = new sqlite3.Database('./todos.db');

The todos.db file will be created if it doesn’t exist.

Once it’s created we can open the file by using the SQLite browser from https://sqlitebrowser.org/.

We can download the file and install it.

Then we can open the todos.db file and run:

CREATE TABLE todos (id INTEGER PRIMARY KEY, name TEXT NOT NULL)

to create the todos table so we can write to it.

Now the SQL statements in our code should run properly.

Then we go into the backend folder and create the .env file.

And then we add the LOGS_TOKEN key so that we can use Sematext for logging:

LOGS_TOKEN=YOUR_OWN_KEY_FROM_SEMATEXT

We can get the key by signing up for an account by going to https://apps.sematext.com/ui/login/.

Once we’re in, we can click on Apps on the left menu.

Then click on New App on the top right to create our app.

Once you see the app entry, we click on the menu button on the right side of the row, and click Integrations.

Then we see Node.js on the left side of what’s open and follow the instructions from there.

Front End

Now that the back end is done, we can move onto the front end.

First, we install the axios HTTP client so that we can make HTTP requests.

We can do that by running:

npm i axios

We go to the frontend/components folder and create a Todo.vue file.

Then we add:

<template>
  <form @submit.prevent="save">
    <input type="text" v-model="todo.name" />
    <input type="submit" value="save" />
    <button type="button" @click="deleteTodo" v-if='todo.id'>delete</button>
  </form>
</template>

<script>
import axios from "axios";
const APIURL = "http://localhost:3000";

export default {
  name: "Todo",
  props: {
    todo: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      name: "",
    };
  },
  methods: {
    async save() {
      const { name } = this.todo;
      if (this.todo.id) {
        await axios.put(`${APIURL}/todos/${this.todo.id}`, { name });
      } else {
        await axios.post(`${APIURL}/todos`, { name });
      }
      this.$emit("saved-todo");
    },

    async deleteTodo() {
      const { data } = await axios.delete(`${APIURL}/todos/${this.todo.id}`);
      this.$emit("saved-todo");
    },
  },
};
</script>

to it.

We use the form to add or edit the files.

Also, we have a delete button to delete the todos.

The save method makes a PUT request to our back end if the todo has an id.

This mneans it’s an existing entry, so we make a PUT request.

Otherwise, we make a POST request.

Once they’re successful, then we emit the save-todos event so that we can get the latest data later.

Also, we have the deleteTodo method so that we can make a DELETE request to delete a item.

The Todo component takes a todo prop, which is optional.

We have the default method to return the default value for it.

The v-if in the template checks if the todo.id exists.

If it does, then it’s displayed so we can call delete to delete the todo item.

We also have a @subnmit.prevent directive to let us submit our form and run the save method.

Next we work on the App.vue file.

We write:

<template>
  <div>
    <Todo @saved-todo="getTodos"></Todo>
    <Todo @saved-todo="getTodos" v-for="t of todos" :todo="t" :key="t.id"></Todo>
  </div>
</template>

<script>
import axios from "axios";
import Todo from "./components/Todo.vue";
const APIURL = "http://localhost:3000";

export default {
  name: "App",
  components: {
    Todo,
  },
  data() {
    return {
      todos: [],
    };
  },
  beforeMount() {
    this.getTodos();
  },
  methods: {
    async getTodos() {
      const { data } = await axios.get(`${APIURL}/todos`);
      this.todos = data;
    },
  },
};
</script>

We add the Todo component that we created earlier.

The first one is for adding the todo item.

The getTodos method lets us get the todo items.

It’s run when the saved-todo event is emitted.

We listen to the event with @saved-todo on the template.

Also, we call getTodos in the beforeMount hook so that we can get the todos when the page loads.

Running Our App

Once this is done, we can run our app.

We first go into the backend folder and run:

npm start

Then we go into the frontend folder and run:

npm run dev

Once that’s done, we can go to http://localhost:3001 to see the app.

Now we should see:

when we go to http://localhost:3001 and when we do something in our app, we see something like:

logged in Sematext.

It’ll log your activities.

Conclusion

Using Sematext with an Express app is easy.

We just use the sematext-express-agent package to let us log with it.

Creating a Vue front end is also easy.

Vue 3 is the up and coming version of Vue. It’s almost ready for production.

Categories
React

Creating Dropdown Menus with React Select

React Select is a dropdown menu library for React apps.

It supports many things that aren’t supported by regular dropdowns.

In this article, we’ll look at how to add menus with React Select.

Getting Started

We can install the package by running:

yarn add react-select

or:

npm i react-select

Then we can use it by writing:

import React from "react";
import Select from "react-select";

const options = [
  { value: "apple", label: "Apple" },
  { value: "orange", label: "Orange" },
  { value: "grape", label: "Grape" }
];

export default function App() {
  return (
    <>
      <Select options={options} />
    </>
  );
}

We add an options array with each entry having the value and label properties.

Then we pass that into the Select component.

Now the labels are displayed in the dropdown.

Animation

We can add animation with the makeAnimated function.

For instance, we can write:

import React from "react";
import Select from "react-select";
import makeAnimated from "react-select/animated";
const animatedComponents = makeAnimated();

const options = [
  { value: "apple", label: "Apple" },
  { value: "orange", label: "Orange" },
  { value: "grape", label: "Grape" }
];

export default function App() {
  return (
    <>
      <Select options={options} components={animatedComponents} />
    </>
  );
}

We pass the animatedComponents as the value of the components prop and we’ll see some animation.

Custom Styles

We can add custom styles with an object.

For example, we can write:

import React from "react";
import Select from "react-select";

const dot = (color = "#ccc") => ({
  alignItems: "center",
  display: "flex",

":before": {
    backgroundColor: color,
    borderRadius: 10,
    content: '" "',
    display: "block",
    marginRight: 8,
    height: 10,
    width: 10
  }
});

const styles = {
  control: styles => ({ ...styles, backgroundColor: "white" }),
  option: (styles, { data, isDisabled, isFocused, isSelected }) => {
    return {
      ...styles,
      backgroundColor: "green",
      color: "#ccc",
      cursor: isDisabled ? "not-allowed" : "default",
      ":active": {
        ...styles[":active"],
        backgroundColor: "orange"
      }
    };
  },
  input: styles => ({ ...styles, ...dot() }),
  placeholder: styles => ({ ...styles, ...dot() }),
  singleValue: (styles, { data }) => ({ ...styles, ...dot(data.color) })
};

const options = [
  { value: "apple", label: "Apple" },
  { value: "orange", label: "Orange" },
  { value: "grape", label: "Grape" }
];

export default function App() {
  return (
    <>
      <Select options={options} styles={styles} />
    </>
  );
}

We create a dot function that returns an object with some styles for a dot.

The :before has everything that creates a dot to the left of the text.

styles has various properties for the controls, options, input, placeholder, and selected values.

We can style according to the isFocused property which indicates whether the choice is in focus.

isSelected indicates whether the choice is selected.

data is the data of the item.

We then pass all that into the styles prop.

Multiselect

We can enable multiple selection with the isMulti prop.

For instance, we can write:

import React from "react";
import Select from "react-select";

const options = [
  { value: "apple", label: "Apple" },
  { value: "orange", label: "Orange" },
  { value: "grape", label: "Grape" }
];

export default function App() {
  return (
    <>
      <Select options={options} isMulti />
    </>
  );
}

Now we can pick multiple choices from the list.

Loading Choices Asynchronously

We can load choices in an async manner.

To do that, we use the AsyncSelect component:

import React from "react";
import AsyncSelect from "react-select/async";

const options = [
  { value: "apple", label: "Apple" },
  { value: "orange", label: "Orange" },
  { value: "grape", label: "Grape" }
];

const filterOptions = inputValue => {
  return options.filter(i =>
    i.label.toLowerCase().includes(inputValue.toLowerCase())
  );
};

const loadOptions = (inputValue, callback) => {
  setTimeout(() => {
    callback(filterOptions(inputValue));
  }, 1000);
};

export default function App() {
  const [inputValue, setInputValue] = React.useState("");
  const handleInputChange = newValue => {
    const inputValue = newValue.replace(/W/g, "");
    setInputValue(inputValue);
    return inputValue;
  };

  return (
    <div>
      <pre>inputValue: "{inputValue}"</pre>
      <AsyncSelect
        cacheOptions
        loadOptions={loadOptions}
        defaultOptions
        onInputChange={handleInputChange}
      />
    </div>
  );
}

We gave the filterOptions method to filter by the value.

loadOptions lets us load the options asynchronously.

It has the inputValue parameter which will be passed in when we type in something.

In the App component, we set the inputValue with the input box.

Also, we have the cacheOptions to cache the options.

defaultOptions determines when the request for the options is fired.

Conclusion

We can create a dropdown easily with React Select.

We can create a dropdown that supports multiple selections, styling, and async loading of options.

Categories
Vue 3

Getting Started with Vue Router with Vue 3

Vue 3 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Getting Started

To get started, we can create our app by including the scripts and accessing the required methods within the VueRouter object.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">Go to Foo</router-link>
        <router-link to="/bar">Go to Bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = { template: "<div>foo</div>" };
      const Bar = { template: "<div>bar</div>" };

      const routes = [
        { path: "/foo", component: Foo },
        { path: "/bar", component: Bar }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the script tags for Vue 3 and Vue Router 4 in the head tag.

In the body , we have the div with ID app to hold the template.

The router-link and router-view components are used for the links and the router view respectively.

Then we call VueRouter.createRouter to create the router and pass in the route.

The VueRouter.createWebHistory method turns on history mode to let us use URLs without the hash sign.

Then we call createApp to create our app.

And we include the router with app.use .

Then finally we mount the app.

Now we can click on the links and see the corresponding component’s content.

The routes have the path and component properties to map the path to the components.

We can access the router data with the this.$router.currentRouter.value.params property.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo/1">Go to Foo</router-link>
        <router-link to="/bar">Go to Bar</router-link>
      </p>
      <p>{{id}}</p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = { template: "<div>foo</div>" };
      const Bar = { template: "<div>bar</div>" };

      const routes = [
        { path: "/foo/:id", component: Foo },
        { path: "/bar", component: Bar }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({
        computed: {
          id() {
            return (
              this.$router.currentRoute.value.params &&
              this.$router.currentRoute.value.params.id
            );
          }
        }
      });
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the /foo/1 URL in the router link.

The :id is the placeholder for the URL parameter called id .

We can get the value of it with the this.$router.current.value.params object.

This has all the URL parameter keys and values.

Conclusion

We can add basic routes with Vue Router 4 with Vue 3.

Categories
Vue 3

Vue Router 4 — Navigation

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Navigation

We can navigate to a route with the this.$router ‘s methods.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">Go to Foo</router-link>
        <router-link to="/bar">Go to Bar</router-link>
        <a @click="goBack" href="#">Go Back</a>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = { template: "<div>foo</div>" };
      const Bar = { template: "<div>bar</div>" };

      const routes = [
        { path: "/foo", component: Foo },
        { path: "/bar", component: Bar }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({
        methods: {
          goBack() {
            window.history.length > 1
              ? this.$router.go(-1)
              : this.$router.push("/foo");
          }
        }
      });
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to create a goBack method and call it when we click on the Go Back link.

In the method, we check if there are anything in the browser history.

If there is, then we go back to the previous page.

Otherwise, we go to the /foo page.

Reacting to Params Changes

We can react to route params changes in a few ways.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo/1">Foo 1</router-link>
        <router-link to="/foo/2">Foo 2</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>",
        watch: {
          $route(to, from) {
            console.log(to, from);
          }
        }
      };

      const routes = [{ path: "/foo/:id", component: Foo }];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the /foo/:id route that maps to the Foo component.

And in the Foo component, we have the $route watcher to watch the route.

to has the route to go to. from has the route that we departed from.

They’re both objects with the path, route metadata, route parameters, query parameters, and more.

Also, we can use the beforeRouteUpdate method to watch for route changes:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo/1">Foo 1</router-link>
        <router-link to="/foo/2">Foo 2</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>",
        beforeRouteUpdate(to, from, next) {
          console.log(to, from);
          next();
        }
      };

      const routes = [{ path: "/foo/:id", component: Foo }];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

Instead of watching the $route object, we have the beforeRouteUpdate hook, which is made available with the app.use(router) call.

Now when we click on the links, we’ll see the same data as we did with the watcher.

The only difference is that we call next to move to the next route.

Conclusion

We can watch for navigation with watchers or hooks with Vue Router 4.