Categories
JavaScript

Introducing the JavaScript Window Object — Location Property

The window object is a global object that has the properties pertaining to the current DOM document, which is the things that are in the tab of a browser.

The document property of the window object has the DOM document and associated nodes and methods that we can use to manipulate the DOM nodes and listen to events for each node.

In this article, we will look at the location.assign , location.reload, location.replace, and location.toString methods. Also, we will look at the document.readyState, document.referrer , and the document.title properties.

Methods of the window.document.location Object

location.assign

The location.assign method causes the window to load and display the document with the given URL. If location.assign fails to run because of a security violation, then the DOMException of the type SECURITY_ERROR will be thrown.

This happens if the script that calls this method has a different origin than the currently loaded page. The most probable cause of this is that the script is hosted on a different domain.

This is needed to prevent unauthorized scripts from taking users to URLs that they didn’t ask for, exposing them to malicious sites. It takes one argument, which is the string with the URL that we want to load. For example, if we want to go to https://medium.com then we can write:

document.location.assign('https://medium.com');

location.reload

To reload the current page, we can use the location.reload method. It works exactly like when you press the Refresh button on your browser. In some browsers, this method takes an option boolean argument, which is false by default. If it’s true , then the page is always reloaded from server, bypassing the browser’s HTTP cache. The call for this method may be blocked and a SECURITY_ERROR type of the DOMException will be thrown if the origin that calls the reload method is different from the origin of the URL of the current loaded page. This is needed to prevent unauthorized scripts from taking users to URLs that they didn’t ask for, exposing them to malicious sites. We can call the reload method like in the following code:

location.reload(true);

location.replace

The location.replace method loads the new URL that’s passed into this function. If we use the replace method, then the page that we were in before the replace method is called won’t be saved in the session History unlike the assign method. This means that the user won’t be able to click the back button to go back to the page that was loaded this method was called. The call for this method may be blocked and a SECURITY_ERROR type of the DOMException will be thrown if the origin that calls the reload method is different from the origin of the URL of the currently loaded page. This is needed to prevent unauthorized scripts from taking users to URLs that they didn’t ask for, exposing them to malicious sites. It takes one argument, which is a string for the URL to load. If the URL isn’t valid, then a SYNTAX_ERROR type of the DOMException will be thrown. For example, if we want to go to https://medium.com then we can write:

document.location.replace('https://medium.com');

location.toString

The toString method returns a string of the whole URL. It’s a read only version of the href property. For example, we can call it like in the following code:

console.log(document.location.toString());

Then we get something the whole URL which is something like https://fiddle.jshell.net/_display/ .

window.document.readyState

To get the loading state of the document object we can use the readyState property. It is a read only string property that get can be set to one of the 3 values:

  • 'loading' — the document is loading
  • 'interactive' — the document is loaded and the document is parsed but sub-resources such as images, stylesheets, and frames are still loading.
  • 'complete' — this document and sub-resources such as images, stylesheets, and frames are all loaded. This state indicates that the load event is about to be triggered.

We can watch the state of the readyState property with a handler for the readystatechange event. One way to write it is to assign an event handler function to the onreadystatechange property. For example, we can write:

document.onreadystatechange = function() {
  console.log(document.readyState);
}

We can also use the addEventListener method to attach the readystatechange event listener, like in the following code:

document.addEventListener('readystatechange', event => {
  console.log(event.target.readyState);
})

window.document.referrer

The referrer property is a read-only string property that returns the URI of the page that’s linked to the currently loaded page. An empty string is returned if the user has navigated to the page directly. That is, the user didn’t navigate to the page through a link, but rather by doing something like typing in the URL directly or clicking on a bookmark. Inside an iframe , the document.referrer will initially be set to the same value as the href of the parent window’s window.location . For example, if we log the document.referrer like in the following code:

console.log(document.referrer)

We may get a URL like https://jsfiddle.net/.

window.document.title

We can use the title property of the document object to get the title of a web page. It’s a property that can get or set. When we use it as a getter, we get a string with the title of the web page. The value set by assigning a string to the document.title takes precedence over the title in the title tag of the HTML markup, so the title that’s set programmatically will be returned if it’s set, otherwise, the one in the HTML markup will be returned. For example, if we set the title with JavaScript by writing the following:

document.title = 'New Title';

Then we get ‘New Title’ on the top of the browser tab. Then when we log the title with console.log(document.title); we also get ‘New Title’. If we didn’t set the title with document.title , then we title in the HTML markup. In Gecko-based browsers, this property applies to HTML, SVG, XUL and other documents.

For XUL, the title attribute of the <xul:window> or other top-level XUL element will have the value of document.title set. In XUL, accessing the document.title before the document if fully loaded has undefined behavior. It may return an empty string and setting document.title may have no effect.

Conclusion

In this article, we looked at various method properties of the location object. The location.assign let us go to another page with a different URL while keeping the currently loaded page in history.

The location.reload method will reload the page, with some browsers accepting a boolean argument that will bypass the cache when reloading the true is passed in.

Like location.assign, the location.replace method let us go to another page with a different URL but the currently loaded page won’t be kept in the history. The location.toString method gets the full URL in string form.

We also looked at some other properties of the document object. The document.readyState let us know the state of the page that’s being loaded.

The document.referrer property is handy for getting the URL of the page that triggered the loading of the currently loaded page, and the document.title property let us get and set the title to the currently loaded web page.

Categories
JavaScript

A Complete Guide to the document.location Property and Location Object in JavaScript

The window object is a global object that has the properties pertaining to the current DOM document, which are the things that are in the tab of a browser.

In this article, we will look at the properties of the window.document object, including the properties of the window.document.location object.

window.document.location

The document.location is a read-only property returns a Location object, which is information about the URL of the current document and gives us methods for changing and loading URLs.

Even though it’s a read-only Location object, if we assign a string to it, it will load the URL in the string.

For example, if we want to load 'https://medium.com' , we can assign it straight to the document.location property as in the following code:

document.location = '[https://medium.com'](https://medium.com%27);

This is the same as assigning the same URL to the document.location.href property:

document.location.href = '[https://medium.com'](https://medium.com%27);

Both pieces of code will load https://medium.com. The Location object has the following properties:

  • Location.href
  • Location.protocol
  • Location.host
  • Location.hostname
  • Location.port
  • Location.pathname
  • Location.search
  • Location.hash
  • Location.username
  • Location.password
  • Location.origin

Location.href

The location.href property is a string that has the whole URL. We can both use it to get the URL of the current page and set the URL so that we can go to the next page. For example, if we have:

console.log(location.href);

Then we get the full URL, which is something like:

[https://fiddle.jshell.net/_display/](https://fiddle.jshell.net/_display/)

We can also use it to go to a different page. For example, we can write:

document.location.href = '[https://medium.com'](https://medium.com%27);

To go to the Medium website. It does the same thing as:

document.location = '[https://medium.com'](https://medium.com%27);

If the current document isn’t in a browsing context, then the value of this property is null.

Location.protocol

We can use the protocol property to get the protocol portion of the URL, which is the very first part of the URL before the first colon (:). For example, we can use it like in the following code:

console.log(document.location.protocol);

Then we get https: for an HTTPS web page and http: for HTTP pages. We can also set the protocol like in the following code:

document.location.protocol = 'http';

If the code above is run, the browser will attempt to go to the HTTP version of the current page.

Location.host

The host property has the string of the hostname. If there’s a port with the hostname, that will also be included. For example, we can retrieve hostname like in the following:

console.log(document.location.host);

Then we get something like fiddle.jshell.net from the console.log statement. We can also set the host property. If we write something like the following code:

document.location.host = 'medium.com';

Then the browser will switch the hostname for the current page with the new one and attempt to load the URL with the new hostname.

Location.hostname

The hostname property is a string property that contains the domain of the URL. For example, we can get the domain by running:

console.log(document.location.hostname);

Then we get something like fiddle.jshell.net from the console.log statement. We can also set the host property. If we write something like the following code:

document.location.hostname = 'medium.com';

Then the browser will switch the domain for the current page with the new one and attempt to load the URL with the new hostname.

Location.port

The port property lets us get and set the port of the URL. It is a string property. If the URL doesn’t have a port number then it’ll be set to an empty string. For example, if we have:

console.log(document.location.port);

Then we get something like “3000” if the port is 3000. We can also set the host property. If we write something like the following code:

document.location.port = '3001';

Then the browser will switch the port for the current page with the new one and attempt to load the URL with the new port number.

Location.pathname

The pathname property is a string property that has the slash followed by the path of the URL, which is everything after the domain. The value will be an empty string if there’s no path. For example, if we have:

console.log(document.location.`pathname`);

Then we get something like “/_display/”. We can also set the pathname property. If we write something like the following code:

document.location.pathname = '3001';

Then the browser will switch the path for the current page with the new one and attempt to load the URL with the new path.

Location.search

The search property is a string property that gets us the query string. The query string is the part of the URL after the ?. For example, we can get the query string part of the URL of the currently loaded page by running:

console.log(document.location.search);

Then we get something like:

"?key=value"

If we have a URL like http://localhost:3000/?key=value. To parse and manipulate the query string we can convert it to a URLSearchParams object. If we want to go to a URL with a different query string, we can assign a new query string to the document.location.search property like we do in the following code:

document.location.search = '?newKey=newValue';

Then the new URL for our browser tab will be http://localhost:3000/?newKey=newValue.

Location.hash

The hash property let us get and set the part of the URL after the pound sign (#). The hash isn’t percent decoded. If there’s no hash fragment, then the value will be an empty string. For example, we can get the query string part of the URL of the currently loaded page by running:

console.log(document.location.hash);

Then we get something like:

"#hash"

if we have a URL like http://localhost:3000/?key=value. If we want to go to a URL with a different query string, we can assign a new query string to the document.location.hash property like we do in the following code:

document.location.hash= '#newHash';

Then the new URL for our browser tab will be http://localhost:3000/?newKey=newValue.

Location.username

The username property gets us the username portion of the URL returned as a string, which is the part between the protocol and the colon. For example, if we went to http://username:password@localhost:3000/, then document.location.username will get us 'username' . If we assign to it like wit the following code:

document.location.username = 'newUsername'

Then the new page will be http://newUsername:password@localhost:3000/.

Location.password

The password property gets us the username portion of the URL returned as a string, which is the part between the protocol and the colon. For example, if we went to http://username:password@localhost:3000/, then document.location.passwordwill get us 'password' . If we assign to it like wit the following code:

document.location.password= 'newPassword'

Then the new page will be http://username:newPassword@localhost:3000/.

Location.origin

The origin property is a string read-only property that has the origin of the represented URL.

For URLs that are with http or https , then it’ll include the protocol followed by :// , followed by the domain, followed by a colon, then followed by the port if it’s explicitly specified.

For a URL that has the file: scheme, then the value is browser dependent. For blob URLs, then the origin of the URL will be the part following blob: . For example, we can log the origin property like in the following code:

console.log(document.location.origin);

to get something like:

[https://fiddle.jshell.net](https://fiddle.jshell.net)

The window.document.location object is an object that has the URL of the current page.

The location object let us various parts of the URL of the current page, and also set them so that the browser will switch out the part that’s designated by the property name and then go to the page with the new URL.

There are also methods to do various things to the currently loaded page, so stay tuned for the next part of this series.

Categories
React

How to Add Geolocation to a React App

Many apps want to get data based on location. This is where the HTML Geolocation API comes in. You can use it easily to get the location of the current device via the Internet.

To get the location of the device the browser is running with plain JavaScript, we write:

if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(getPosition);
}

function getPosition(position) {
  console.log(position.coords.latitude, position.coords.longitude);
}

As you can see, getting the latitude and longitude is very easy. We can also easily add geolocation to any React app. The react-geolocated package is a great add-on for adding geolocation capabilities to your app. It is located at https://www.npmjs.com/package/react-geolocated.

It provides a promise-based API for getting the location of the device, so we can easily use async and await to get the location with this package.

In this article, we will build an app to weather app that lets you get the current weather and forecast of the current location you’re in. It also lets you search the weather by inputting the location manually.

To start we will run Create React App to create the React project. Run:

npx create-react-app weather-app

to create the project.

Next, we have to add some libraries. We need Axios for making HTTP requests, Formik and Yup for form value handling and validation, MobX for state management, Bootstrap for styling, React Geolocated for getting geolocation data, and React Router for routing. We install them by running:

npm i axios formik mobx mobx-react react-bootstrap react-geolocated react-router-dom yup

With all the packages installed, we can start building the app. We start by replacing the code in App.css :

.page {
  padding: 20px;
}

to add some padding to our page.

Next, replace the code in App.js with:

import React from "react";
import { Router, Route } from "react-router-dom";
import HomePage from "./HomePage";
import { createBrowserHistory as createHistory } from "history";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import "./App.css";
const history = createHistory();

function App({ keywordStore }) {
  return (
    <div className="App">
      <Router history={history}>
        <Navbar bg="primary" expand="lg" variant="dark">
          <Navbar.Brand href="#home">Geolocation Weather App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link href="/" active>
                Home
              </Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Route
          path="/"
          exact
          component={props => (
            <HomePage {...props} keywordStore={keywordStore} />
          )}
        />
      </Router>
    </div>
  );
}

export default App;

Here we add the React Bootstrap navigation bar and the route to our home page.

Next, we create a component to display the current weather. Create a file called CurrentWeather.js in the src folder and add:

import React from "react";
import { observer } from "mobx-react";
import { searchWeather } from "./request";
import ListGroup from "react-bootstrap/ListGroup";

function CurrentWeather({ keywordStore }) {
  const [weather, setWeather] = React.useState({});

  const getWeatherForecast = async keyword => {
    const response = await searchWeather(keyword);
    setWeather(response.data);
  };

  React.useEffect(() => {
    keywordStore.keyword && getWeatherForecast(keywordStore.keyword);
  }, [keywordStore.keyword]);

  return (
    <div>
      {weather.main ? (
        <ListGroup>
          <ListGroup.Item>
            Current Temparature: {weather.main.temp - 273.15} C
          </ListGroup.Item>
          <ListGroup.Item>
            High: {weather.main.temp_max - 273.15} C
          </ListGroup.Item>
          <ListGroup.Item>
            Low: {weather.main.temp_min - 273.15} C
          </ListGroup.Item>
          <ListGroup.Item>Pressure: {weather.main.pressure} </ListGroup.Item>
          <ListGroup.Item>Humidity: {weather.main.humidity}</ListGroup.Item>
        </ListGroup>
      ) : null}
    </div>
  );
}
export default observer(CurrentWeather);

We get the search keyword from the keywordStore which we will build and search for the current weather when keywordStore.keyword is updated by passing it in the array of the second argument of the React.useEffect function.

Once the data is retrieved, we set the data with the setWeather function and display the data in a ListGroup at the bottom of the page.

We wrap observer around our component in the last line so that we get the latest keyword value from keywordStore .

Next, we add a component for display the forecast, add Forecast.js in the src folder and add:

import React from "react";
import { observer } from "mobx-react";
import { searchForecast } from "./request";
import ListGroup from "react-bootstrap/ListGroup";
import Card from "react-bootstrap/Card";

function Forecast({ keywordStore }) {
  const [forecast, setForecast] = React.useState({});

  const getWeatherForecast = async keyword => {
    const response = await searchForecast(keyword);
    setForecast(response.data);
  };

  React.useEffect(() => {
    keywordStore.keyword && getWeatherForecast(keywordStore.keyword);
  }, [keywordStore.keyword]);

  return (
    <div>
      {Array.isArray(forecast.list) ? (
        <div>
          {forecast.list.map(l => {
            return (
              <Card body>
                <ListGroup>
                  <ListGroup.Item>Date: {l.dt_txt}</ListGroup.Item>
                  <ListGroup.Item>
                    Temperature: {l.main.temp - 273.15} C
                  </ListGroup.Item>
                  <ListGroup.Item>
                    High: {l.main.temp_max - 273.15} C
                  </ListGroup.Item>
                  <ListGroup.Item>
                    Low: {l.main.temp_min - 273.15} C
                  </ListGroup.Item>
                  <ListGroup.Item>Pressure: {l.main.pressure} C</ListGroup.Item>
                </ListGroup>
              </Card>
            );
          })}
        </div>
      ) : null}
    </div>
  );
}
export default observer(Forecast);

It’s very similar to CurrentWeather.js except that the data is in a list. So we map forecast.list into an array of Card s instead of just rendering it.

Next, we create a HomePage.js file in the src folder and add:

import React from "react";
import { Formik } from "formik";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import Button from "react-bootstrap/Button";
import { observer } from "mobx-react";
import * as yup from "yup";
import Tabs from "react-bootstrap/Tabs";
import Tab from "react-bootstrap/Tab";
import CurrentWeather from "./CurrentWeather";
import Forecast from "./Forecast";
import { geolocated } from "react-geolocated";
import { getLocationByLatLng } from "./request";

const schema = yup.object({
  keyword: yup.string().required("Keyword is required")
});

const buttonStyle = { marginRight: "10px" };

function HomePage({ keywordStore, coords }) {
  const [initialized, setInitialized] = React.useState(false);

  const handleSubmit = async evt => {
    const isValid = await schema.validate(evt);
    if (!isValid) {
      return;
    }
    localStorage.setItem("keyword", evt.keyword);
    keywordStore.setKeyword(evt.keyword);
  };

  const getWeatherCurrentLocation = async () => {
    const { latitude, longitude } = coords;
    const { data } = await getLocationByLatLng(latitude, longitude);
    const keyword = (
      data.results[0].address_components.find(c =>
        c.types.includes("locality")
      ) || {}
    ).long_name;
    localStorage.setItem("keyword", keyword);
    keywordStore.setKeyword(keyword);
  };

  const clear = () => {
    localStorage.clear();
    keywordStore.setKeyword("");
  };

  React.useEffect(() => {
    if (!initialized) {
      keywordStore.setKeyword(localStorage.getItem("keyword") || "");
      setInitialized(true);
    }
  }, [keywordStore.keyword]);

  return (
    <div className="page">
      <h1>Weather</h1>
      <Formik
        validationSchema={schema}
        onSubmit={handleSubmit}
        initialValues={{ keyword: localStorage.getItem("keyword") || "" }}
        enableReinitialize={true}
      >
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors
        }) => (
          <Form noValidate onSubmit={handleSubmit}>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="keyword">
                <Form.Label>City</Form.Label>
                <Form.Control
                  type="text"
                  name="keyword"
                  placeholder="City"
                  value={values.keyword || ""}
                  onChange={handleChange}
                  isInvalid={touched.keyword && errors.keyword}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.keyword}
                </Form.Control.Feedback>
              </Form.Group>
            </Form.Row>
            <Button type="submit" style={buttonStyle}>
              Search
            </Button>
            <Button
              type="button"
              onClick={getWeatherCurrentLocation}
              style={buttonStyle}
              disabled={!coords}
            >
              Get Weather of Current Location
            </Button>
            <Button type="button" onClick={clear}>
              Clear
            </Button>
          </Form>
        )}
      </Formik>
      <br />
      <Tabs defaultActiveKey="weather">
        <Tab eventKey="weather" title="Current Weather">
          <CurrentWeather keywordStore={keywordStore} />
        </Tab>
        <Tab eventKey="forecast" title="Forecast">
          <Forecast keywordStore={keywordStore} />
        </Tab>
      </Tabs>
    </div>
  );
}

HomePage = observer(HomePage);

export default geolocated({
  positionOptions: {
    enableHighAccuracy: false
  },
  userDecisionTimeout: 5000
})(HomePage);

We use the React-Geolocated package here to add the geolocation functionality to our app. The package provides us with the geolocated higher-order component to let users get their current location if they allow our app to do so. We set enableHighAccuracy to false so that our app will respond faster by using a less accurate location, which is already accurate in most cases. userDecisionTimeout is set so that after 5 seconds, geolocation will be assumed to be disabled by the user.

Since we wrapped our HomePage component with the geolocated component, we will get the coords prop so that we get the location when geolocation is enabled. We add check for the coords prop in our ‘Get Weather of Current Location‘ button so that users can only use the geolocation functionality when they have geolocation enabled.

In the callback for the useEffect hook, we set the array in the second argument to watch for keywordStore.keyword changes so that it will re-render when that changes. We need this so that we get the latest keyword into our form via the intialValues prop of our Formik component. When we click the ‘Get Weather of Current Location‘ button, we get set the keyword in both local storage and the MobX store, so we can just watch the store to make it render the local storage’s keyword value.

In the getWeatherCurrentLocation function, we get the location with the Google Maps API, then get the city name by finding the component with locality in the types property, and get the long_name from that, then set the keyword in our MobX store for use by the previous components we created and also in local storage so that we see it in our form and keep the same value set after refreshing the value.

Next in index.js replace the existing code with:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { KeywordStore } from "./store";
const keywordStore = new KeywordStore();

ReactDOM.render(
  <App keywordStore={keywordStore} />,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: [https://bit.ly/CRA-PWA](https://bit.ly/CRA-PWA)
serviceWorker.unregister();

to pass or MobX store into our app.

Then in we create requests.js in the src folder, and add the following:

const APIURL = "http://api.openweathermap.org";
const GOOGLE_MAP_API_URL = "https://maps.googleapis.com/maps/api/geocode/json";
const axios = require("axios");

export const searchWeather = loc =>
  axios.get(
    `${APIURL}/data/2.5/weather?q=${loc}&appid=${process.env.REACT_APP_APIKEY}`
  );

export const searchForecast = loc =>
  axios.get(
    `${APIURL}/data/2.5/forecast?q=${loc}&appid=${process.env.REACT_APP_APIKEY}`
  );

export const getLocationByLatLng = (lat, lng) =>
  axios.get(
    `${GOOGLE_MAP_API_URL}?latlng=${lat},${lng}&key=${process.env.REACT_APP_GOOGLE_APIKEY}`
  );

We have functions to make HTTP requests to get the weather and location with the Google API. To get the properties in process.env , put them in your .env file in your project’s root folder with those field properties as keys and the corresponding API keys as the values.

After that, create store.js in the src folder and add:

import { observable, action, decorate } from "mobx";

class KeywordStore {
  keyword = "";

  setKeyword(keyword) {
    this.keyword = keyword;
  }
}

KeywordStore = decorate(KeywordStore, {
  keyword: observable,
  setKeyword: action
});

export { KeywordStore };

to create our MobX Store. We have the keyword field that is watched by other components by wrappingobserver function outside our component when we export them. Also, we have the setKeyword function to set the latest values of the keyword field.

Finally, in index.js , replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Geolocation Weather App</title>
    <link
      rel="stylesheet"
     href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

to change the title and add Bootstrap CSS.

After all the hard work, we run npm start to start our app. Then we get:

Categories
Vue 3

Vuex 4 — Modules Namespace

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

Vuex is a popular state management library for Vue.

Vuex 4 is the version that’s made to work with Vue 3.

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

Accessing Global Assets in Namespaced Modules

We can access global assets in namespaced modules.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="this['moduleA/increment']">increment</button>
      <p>{{this['moduleA/doubleCount']}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        actions: {
          increment({ commit, dispatch }) {
            commit("increment");
            commit("someAction", null, { root: true });
          }
        },
        getters: {
          doubleCount(state) {
            return state.count * 2;
          }
        }
      };

      const store = new Vuex.Store({
        mutations: {
          someAction(state) {
            console.log("someAction");
          }
        },
        modules: {
          moduleA: {
            namespaced: true,
            ...moduleA
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapActions(["moduleA/increment"])
        },
        computed: {
          ...Vuex.mapGetters(["moduleA/doubleCount"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We have the increment action that commits the someAction mutation from the root namespace.

Therefore, when we dispatch the moduleA/increment action, we should see 'someAction' logged.

We called commit with an object with the root: true property to set make it dispatch the root mutation.

We can do the same with actions. For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="this['moduleA/increment']">increment</button>
      <p>{{this['moduleA/doubleCount']}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        actions: {
          increment({ commit, dispatch }) {
            commit("increment");
            dispatch("someOtherAction", null, { root: true });
          }
        },
        getters: {
          doubleCount(state) {
            return state.count * 2;
          }
        }
      };

      const store = new Vuex.Store({
        mutations: {
          someAction(state) {
            console.log("someAction");
          }
        },
        actions: {
          someOtherAction({ commit }) {
            commit("someAction");
          }
        },
        modules: {
          moduleA: {
            namespaced: true,
            ...moduleA
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapActions(["moduleA/increment"])
        },
        computed: {
          ...Vuex.mapGetters(["moduleA/doubleCount"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We called dispatch with an object with the root: true property to set make it dispatch the root action.

We can access root getters with the rootGetters property.

To do that, we write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="this['moduleA/increment']">increment</button>
      <p>{{this['moduleA/doubleCount']}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        actions: {
          increment({ commit, dispatch, rootGetters }) {
            console.log("increment", rootGetters.one);
            commit("increment");
          }
        },
        getters: {
          doubleCount(state, getters, rootState, rootGetters) {
            console.log("doubleCount", rootGetters.one);
            return state.count * 2;
          }
        }
      };

      const store = new Vuex.Store({
        getters: {
          one(state) {
            return 1;
          }
        },
        modules: {
          moduleA: {
            namespaced: true,
            ...moduleA
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapActions(["moduleA/increment"])
        },
        computed: {
          ...Vuex.mapGetters(["moduleA/doubleCount"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We have a one root getter method in the store’s root.

Then we have the rootGetters property in the object parameter of the increment method.

We can get the getter’s return value with from the rootGetters.one property.

Other getters can get the value from the rootGetters parameter.

Conclusion

We can namespace our store so that we divide our store into smaller chunks that have their own states.

Categories
Vue 3

Vuex 4 — Modules

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

Vuex is a popular state management library for Vue.

Vuex 4 is the version that’s made to work with Vue 3.

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

Modules

We can divide our Vuex 4 store into modules so that we can divide our states into multiple smaller parts.

Each module has their own state, mutations, actions, and getters.

Module Local State

Each module has their own local state. We can map the module’s state into computed properties and mutations into methods by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="increment">increment</button>
      <p>{{doubleCount}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        getters: {
          doubleCount(state) {
            return state.count * 2;
          }
        }
      };

      const store = new Vuex.Store({
        modules: {
          moduleA
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapMutations(["increment"])
        },
        computed: {
          ...Vuex.mapGetters(["doubleCount"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We created the store with the modules property to include our module in the store.

Then in our Vue instance, we have the mapMutations and mapGetters methods to map the mutations to methods and the getters to computed properties.

We can do the same with actions and states.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="increment">increment</button>
      <p>{{moduleA.count}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        actions: {
          increment({ commit }) {
            commit("increment");
          }
        }
      };

      const store = new Vuex.Store({
        modules: {
          moduleA
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapActions(["increment"])
        },
        computed: {
          ...Vuex.mapState({ moduleA: (state) => state.moduleA })
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We called the mapActions method with an array of action names.

The mapState method is called with an object with methods to return the module states.

The states are in the object with the module name as the property name.

Namespacing

We can namespace our modules.

This way, we won’t have everything under one global namespace.

Also, this lets multiple modules react to the same mutation or action type.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="this['moduleA/increment']">increment</button>
      <p>{{moduleA.count}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        actions: {
          increment({ commit }) {
            commit("increment");
          }
        }
      };

      const store = new Vuex.Store({
        modules: {
          moduleA: {
            namespaced: true,
            ...moduleA
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapActions(["moduleA/increment"])
        },
        computed: {
          ...Vuex.mapState({ moduleA: (state) => state.moduleA })
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We mapped the action with the namespace name since we set the namespaced property to true .

We can do the same with getters and mutations.

To do that, we write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="this['moduleA/increment']">increment</button>
      <p>{{this['moduleA/doubleCount']}}</p>
    </div>
    <script>
      const moduleA = {
        state: () => ({
          count: 0
        }),
        mutations: {
          increment(state) {
            state.count++;
          }
        },
        getters: {
          doubleCount(state) {
            return state.count;
          }
        }
      };

      const store = new Vuex.Store({
        modules: {
          moduleA: {
            namespaced: true,
            ...moduleA
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapMutations(["moduleA/increment"])
        },
        computed: {
          ...Vuex.mapGetters(["moduleA/doubleCount"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We called mapMutations and mapGetters with the namespace name since we set namespaced to true in the module.

Conclusion

We can create our modules and namespace them so that we can have multiple smaller parts of a store that manage their own state.