Max100's avatar

Recommended way of handling unsaved changes

Using Breeze/Vue with Inertia.js, I'm wondering the best way to handle unsaved changes.

The app nav bar has links to switch to different components in the app. If the user is editing a "File", but then clicks on the Calendar navbar link , I want to check for unsaved changes in the file form.

I was thinking of using the onBeforeUnmount() lifecycle hook in the file form to do this, but that may be too late and maybe there's a better approach. Anyway, assuming I use the onBeforeUnmounted() hook, if a change is detected, I can display a dialog to notify the user about unsaved changes and ask if they want to save the changes.

If the user wants to save the changes, I'd like to cancel the switch to the calendar and leave the user on the file form, so they can continue editing or save the form. But I'm not sure how I can cancel the switch to the calendar.

Alternatively, it could just somehow save the changes before the the form component unmounts, but that will require another route, because the file update returns to the file index and it would probably interfere with the switch to the calendar.

I haven't used Pinia and I'm not sure if that could help in this scenario or not. I can detect unsaved changes, but just not sure about the best approach to handle them when the user switches to another component.

Any thoughts or suggestions are greatly appreciated.

0 likes
3 replies
LaryAI's avatar
Level 58

Handling unsaved changes is a common challenge in web applications, and there are several ways to approach it. Here's a recommended solution using Vue with Inertia.js:

  1. Track the form's state to detect changes.
  2. Intercept navigation events to check for unsaved changes.
  3. Prompt the user to save changes or discard them.
  4. Cancel navigation if necessary.

Here's a step-by-step guide with code examples:

Step 1: Track Form State

Use a data property to track the form's initial state and compare it with the current state.

export default {
  data() {
    return {
      form: this.$inertia.form({
        // Your form fields here
      }),
      initialFormState: {},
    };
  },
  created() {
    this.setInitialFormState();
  },
  methods: {
    setInitialFormState() {
      this.initialFormState = JSON.parse(JSON.stringify(this.form));
    },
    hasUnsavedChanges() {
      return JSON.stringify(this.form) !== JSON.stringify(this.initialFormState);
    },
  },
};

Step 2: Intercept Navigation Events

Use a global navigation guard or a local beforeRouteLeave guard to intercept navigation events.

beforeRouteLeave(to, from, next) {
  if (this.hasUnsavedChanges()) {
    if (confirm('You have unsaved changes. Do you want to save them before leaving?')) {
      this.saveForm(next);
    } else {
      next();
    }
  } else {
    next();
  }
},

Step 3: Prompt the User

When unsaved changes are detected, prompt the user to save or discard them.

Step 4: Cancel or Continue Navigation

If the user chooses to save changes, call a method to save the form and pass the next callback to handle navigation after saving.

methods: {
  saveForm(next) {
    this.form.put('/save-url', {
      preserveState: true,
      onSuccess: () => {
        this.setInitialFormState();
        next();
      },
      onError: () => {
        // Handle save errors here
      }
    });
  },
},

If the user chooses to discard changes or the save is successful, call next() to continue navigation.

Using Pinia (Optional)

If you're using Pinia, you can centralize the form state in a store, making it easier to manage and access across components.

// store.js
import { defineStore } from 'pinia';

export const useFileStore = defineStore('file', {
  state: () => ({
    form: {
      // Your form fields here
    },
    initialFormState: {},
  }),
  actions: {
    setInitialFormState() {
      this.initialFormState = JSON.parse(JSON.stringify(this.form));
    },
    hasUnsavedChanges() {
      return JSON.stringify(this.form) !== JSON.stringify(this.initialFormState);
    },
    // Add saveForm method here
  },
});

Then, in your component, you would use the store instead of the component's local state.

import { useFileStore } from './store';

export default {
  setup() {
    const fileStore = useFileStore();
    // Use fileStore in your component
  },
  // Rest of the component
};

Remember to handle the save operation properly, considering the user experience and the application's flow. The above examples are a starting point, and you may need to adjust them to fit your specific use case and handle edge cases.

MohamedTammam's avatar

You can use beforeRouteLeave guard.

Vue documentation has example regarding that: https://router.vuejs.org/guide/advanced/navigation-guards.html#In-Component-Guards

The leave guard is usually used to prevent the user from accidentally leaving the route with unsaved edits. The navigation can be canceled by returning false.

beforeRouteLeave (to, from) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (!answer) return false
}
Max100's avatar

@MohamedTammam That's great, thanks! Hopefully, that also works with inertia router. If not, maybe inertia has something similar.

Thanks again!

Please or to participate in this conversation.