What is the reason for Vue.js failing to refresh the DOM when utilizing the datepicker feature with Moment.js

Currently, I am working on a basic datepicker control in vue.js, utilizing moment.js for managing the date formatting and manipulation.

The issue that I'm encountering is that despite modifying the date instance within the component upon clicking either button, the display does not reflect these changes.

To troubleshoot, I experimented with using a simple integer instead of a date instance, which resulted in the DOM being updated correctly.

Below is the code snippet where I am facing this challenge:

App.js

Vue.component("datePicker", {
    props: ["value"],
    data: function() {
        return { selectedDate: moment(this.value) };
    },
    template: "<div><button v-on:click='decrement'>&lt;</button>{{formattedSelectedDate}}<button v-on:click='increment'>&gt;</button></div>",
    methods: {
        increment: function () {
            this.selectedDate.add(1, "days");
            this.$emit('input', this.selectedDate);
        },
        decrement: function () {
            this.selectedDate.subtract(1, "days");
            this.$emit('input', this.selectedDate);
        }
    },
    computed: {
        formattedSelectedDate: function() {
            return this.selectedDate.format("YYYY-MM-DD");
        }
    }
});

var PointTracker = new Vue({
    el: "#PointTracker",
    data: {
        selectedDate: moment(),
        todoItems: {}
    }
});

Index.html

<html>
    <head>
        <meta charset="utf-8">
        <title>Point Tracker</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>

        <div id="PointTracker">
            <date-picker v-model="selectedDate">
        </div>

        <script src="node_modules/moment/moment.js"></script>
        <script src="node_modules/vue/dist/vue.js"></script>
        <script src="node_modules/vue-resource/dist/vue-resource.js"></script>
        <script src="app.js"></script>
    </body>
</html>

Answer №1

To ensure Vue watchers are triggered, you need to update the reference of selectedDate. When using functions from moment, the returned values have the same reference, which prevents Vue from detecting changes. To change the reference, make the following adjustments:

methods: {
    increment: function () {
        this.selectedDate = moment(this.selectedDate).add(1, "days")
    },
    decrement: function () {
        this.selectedDate = moment(this.selectedDate).subtract(1, "days")
    }
},

See the working example here: http://jsfiddle.net/mimani/pLcfyrvy/1/


The implementation of add/subtract from the moment library is as follows:

function addSubtract (duration, input, value, direction) {
    var other = createDuration(input, value);

    duration._milliseconds += direction * other._milliseconds;
    duration._days         += direction * other._days;
    duration._months       += direction * other._months;

    return duration._bubble();
}

// supports only 2.0-style add(1, 's') or add(duration)
export function add (input, value) {
    return addSubtract(this, input, value, 1);
}

// supports only 2.0-style subtract(1, 's') or subtract(duration)
export function subtract (input, value) {
    return addSubtract(this, input, value, -1);
}

Since it returns the same object, the reference remains unchanged for the date object.


Explanation

This issue occurs because moment.js is not immutable. When calling add/subtract on a moment object, it modifies the same object instead of returning a new one. This affects reactivity in Vue as it cannot detect internal changes in objects unless a new object is created.

For solutions, consider alternatives such as cloning the object or using libraries like frozen-moment.

Answer №2

One issue that arises is the reference to the selectedDate remains constant, even when methods are being called on it.
To address this, one solution is to store the date in a different format such as a date object or a number. This way, each time a change is made, a new instance of the data can be generated.

Below is an example:

var datePicker = Vue.component("datePicker", {
    data: function() {
        return { selectedDate: moment().toDate() }
    },
    template: `<div>
        <button v-on:click='decrement'>&lt;</button>
          {{formattedSelectedDate}}
        <button v-on:click='increment'>&gt;</button>
        {{selectedDate}}
      </div>`,
    methods: {
        increment: function () {
            var currentDate = moment(this.selectedDate);
            currentDate.add(1, "days");
            this.selectedDate = currentDate.toDate();
        },
        decrement: function () {
            var currentDate = moment(this.selectedDate);
            currentDate.subtract(1, "Days");
            this.selectedDate = currentDate.toDate();
        }
    },
    computed: {
        formattedSelectedDate: function() {
            return moment(this.selectedDate).format("YYYY-MM-DD");
        }
    }
});

var PointTracker = new Vue({
    el: "#PointTracker",
    data: {
        date: 0
    },
    components: {
        datePicker
    }
});
<div id="PointTracker">
  <date-picker />
</div>

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.js"></script>

Answer №3

The reason for this situation is the conflicting nature of moment and vue, as both rely on defining objects' getters and setters. This leads to one overriding the other in your specific scenario. (Therefore, modifying the reference works because vm.selectedDate.set gets overridden while vm.set does not)

According to moment's documentation:

Moment.js utilizes overloaded getters and setters.

As per vue's guidelines:

Vue will recursively convert its properties into getter/setters to achieve "reactivity". The object should be simple: native objects like browser API objects and prototype properties are disregarded. A general principle is that data should just remain as data - observing objects with their own stateful behavior is not advised.

A brief confirmation: I inserted

console.log(this.selectedDate.get)
in the fiddle created by saurabh, which resulted in the logged function being stringGet from moment.js. Subsequently, I examined vm.foo's get in my own project, revealing it as proxyGetter, acting as a wrapper for reactiveGetter, both located within vue.runtime.common.js.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error in TypeScript: Typography type does not accept 'string' type as valid

As I attempt to implement the Typography component from material-ui using TypeScript, I encounter a perplexing error message TypeScript is throwing an error: Type 'string' is not assignable to type 'ComponentClass<HTMLAttributes<HTMLE ...

JavaScript Arrays with Four Dimensions

Looking for a solution to generate arrays with any number of dimensions, including 4D arrays. I'm interested in being able to use the function to create an array and then access it like this: arr[3][2][23][12] = "amazing"; ...

Error Message Missing from Google Cloud Function Response

In my Google Cloud function code, I have encountered an issue where the status changes to 400 when there is an error creating a new user. However, on the client side, I always receive an "Error: invalid-argument" message regardless of the actual error from ...

When encountering an OR operator, Javascript will cease execution of the remaining conditions

This is a basic JavaScript form-validation I created. All the document.form.*.value references are present on my page, except for the document.form.dasdasdas.value ==''. In the code below, the purpose is to display an error if any of the forms a ...

Creating a three-dimensional representation by projecting onto the surface of a sphere in ThreeJS

3D animation is a realm filled with numerous terms and concepts that are unfamiliar to me. I am particularly confused about the significance of "UV" in 3D rendering and the tools used for mapping pixels onto a mesh. I have an image captured by a 360-degre ...

VueRouter - The local storage data will be wiped out if a trailing slash is not included in the URL

Storing a JWT token in localStorage has caused an issue where the token is only visible in Developer Tools when visiting https://www.example.com/my-web-app/. However, when visiting https://www.example.com/my-web-app, the data in localStorage remains invisi ...

Monochrome Effect Triggered by Cursor Hover

I'm attempting to use Javascript with HTML5 canvas to convert an image to greyscale, but I seem to be missing something in my code. Can anyone spot the error? I feel like I'm very close! function grayscaleConversion(str) { // Access the Canv ...

Learn the process of sending notifications upon clicking on an advertisement by a user

I'm currently working on a system that displays ads on mobile devices. Our clients have the option to use their own custom HTML code for their advertisements. We want to be able to track when a user clicks on these ads. I am considering wrapping the u ...

Guide on submitting a form in ReactJS with Material UI Dialog

I utilized the Material UI Dialog to create a form list. In the official example: <Dialog title="Dialog With Custom Width" actions={actions} modal={true} open={this.state.open} > This dialog ...

Pausing briefly after selecting an element with Webdriver

I have been attempting to highlight an element on a webpage for a specific duration of time, approximately 5-6 seconds. However, I am facing difficulty in making the highlight persist for the desired duration as it currently flashes briefly. Despite using ...

Error: Failed to clone an object in Electron due to an unhandled promise

I'm in need of assistance with sending files from the main directory to the renderer. Before proceeding, I attempted to check if it was functioning correctly by using console.log(files). However, this resulted in an error message. Can someone please p ...

Why can't we import Angular 4 as a library similar to AngularJS?

Why was AngularJS introduced as a script to import in an HTML page? However, in the newer version Angular 4, we need to use a web server to launch the application? Is it because AngularJS is not considered a framework but Angular 4 is? Thank you. ...

Automatically selecting and fetching checkbox values using JavaScript

I was struggling with coding a function that involved generating checkboxes using JavaScript. My goal is to automatically select the elements "March" and "September" from the array target[] and display them as checked in the text area. So, "March" and "Se ...

the hidden input's value is null

I am encountering an issue with a hidden input in this form. When I submit the form to my API, the value of the input is empty. Isbn and packId are both properties of a book model. However, for some reason, the value of packId is coming out as empty. & ...

Error: anticipated expression, received keyword 'if'

I'm facing an issue that I could really use some assistance with. I am trying to hide the "changeOrderUp" button when it's in the first row, and similarly, I want to hide the "changeOrderDown" button when it's in the last row. However, FireB ...

Calculate the sum of the elements within an array that possess a distinct attribute

I need to calculate the sum of certain elements in an array. For example, let's consider this array: var sampleArray = [ {"id": 1, "value": 50, "active": true}, {"id": 2, "value": 70, "active": false}, ...

Is Riot.js the best choice for server-side rendering and routing?

Currently, I am using Node along with Riot.js and the Grapnel routing library. I have successfully set up routing on the client side, but I am facing difficulty in making it work on the server side. The functioning of my client router is straightforward. ...

What could be the reason for my button to update only at the completion of the "each" loop?

Is there a way to update the button text before the loop, so that users can receive feedback while it's running? Currently, the text only updates after the loop has completed. In my scenario, the button displays "Updating please wait..." after the lo ...

What is the process for importing the required dependencies for the maps module in react-svg-map?

Exploring interactive maps led me to discover the project react-svg-map. As I followed the example provided, a certain issue caught my attention: import Taiwan from "@svg-maps/taiwan"; The developers mentioned that they have separated the map&ap ...

Return true in an incorrect manner

Coderbyte Challenge 7 Need help with a function that checks if every letter in the string is bounded by '+' signs. The code provided seems to be returning incorrect results - it should return false for the input string below as 'k' is ...