Vue Mixins, BootstrapVue, Modals, and the DRY Principle.
Tags: vue
Published: 2020-04-27 16:56:07

Have you ever started a project and realized that you were repeating code all over the place? I was in this situation recently when building a project with Laravel, Vue, and BootstrapVue. I typically use Modals to handle my CRUD operations (Create, Read, Update, Delete) on the client-side and each of my modals was using the same code, that's why I decided to use a Vue Mixin in order to follow the DRY principle (Don't Repeat Yourself)

## What are Vue Mixins and why use them?
Before we jump into the code, let's go over what a Vue Mixin is and how they're useful. Here is the definition straight from the [Vue Documentation](https://vuejs.org/v2/guide/mixins.html).

>*Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.*

For example, I'm using BootstrapVue in my current application and all my modals need a shown() method which is triggered when the @shown event is called. I can add the shown() method into all my modals or I can follow the DRY principle and put the shown() method into a mixin which will be imported and automatically mixed into all my modals. That means that down the road if I have to make a change to this method, I only have to change it in one spot (the mixin) rather then changing it in all my modals.

## Let's see the modal without a mixin
This is a modal that is used to delete a resource and it's before we add in the mixin. The Data Object, ToastMixin, and Axios will repeat if we don't use a mixin.

**DeleteListingModa.vue**
```js
<script>
import ToastMixin from './ToastMixin.js'
export default {
  name: 'DeleteListingModal',
  props: {
    item: {
      type: Object,
      required: true,
      default: () => {}
    }
  },
  mixins: [
    ToastMixin
  ],
  data () {
    return {
      formData: {},
      formErrors: [],
      isLoading: false
    }
  },
  methods: {
    submit (evt) {
      evt.preventDefault()

      this.isLoading = true
      axios.delete('/api/listings/' + this.formData.id).then(() => {
        this.isLoading = false
        this.$root.$emit('getListings)
        this.closeModal()
        this.toast('success', 'Success!', 'Item was deleted successfully!')
      }).catch((error) => {
        this.isLoading = false
        if (error.response.status === 422) {
          this.formErrors = error.response.data.errors
        } else {
          this.toast('danger', 'Something went wrong!', 'Whoops.. Looks like something went wrong.')
        }
      })
    }
  }
}
</script>
```

## Now let's see the modal with the mixin
You can see below that this has cleaned up our modal and we are no longer repeating ourselves.

**DeleteListingModa.vue**

```js
<script>
import ModalActions from '../../../mixins/ModalActions.js'
export default {
  name: 'DeleteListingModal',
  mixins: [
    ModalActions
  ],
  props: {
    item: {
      type: Object,
      required: true,
      default: () => {}
    }
  },
  methods: {
    submit (evt) {
      evt.preventDefault()

      // Accepts two params: URL, name for $emit event.
      this.deleteItem('/api/listings/', 'getUser')
    }
  }
}
</script>
```
**ModalActions.js (the mixin)**
```js
import ToastMixin from './ToastMixin.js'
export default {
  mixins: [
    ToastMixin
  ],
  data () {
    return {
      formData: {},
      formErrors: [],
      isLoading: false
    }
  },
  methods: {
    // Set's formData with default data
    shown () {
      this.formData = this.item
    },

    // Action to open modal and set formData
    openModal (modalName, data = {}) {
      this.formData = data
      this.$root.$emit('bv::show::modal', modalName)
    },

    // Action to close modal
    closeModal () {
      this.$root.$emit('bv::hide::modal', this.$options.name)
    },

    // Method to create item resource
    createItem (url, data = {}, event = '') {
      this.isLoading = true
      axios.post(url, data).then(() => {
        this.isLoading = false
        this.$root.$emit(event)
        this.closeModal()
        this.toast('success', 'Success!', 'Item was created successfully!')
      }).catch((error) => {
        this.isLoading = false
        if (error.response.status === 422) {
          this.formErrors = error.response.data.errors
        } else {
          this.toast('danger', 'Something went wrong!', 'Whoops.. Looks like something went wrong.')
        }
      })
    },

    // Method to update item resource
    updateItem (url, event = '') {
      this.isLoading = true
      axios.put(url + this.formData.id, this.formData).then((response) => {
        this.isLoading = false
        this.$root.$emit(event)
        this.closeModal()
        this.toast('success', 'Success!', 'Item was updated successfully!')
      }).catch((error) => {
        this.isLoading = false
        if (error.response.status === 422) {
          this.formErrors = error.response.data.errors
        } else {
          this.toast('danger', 'Something went wrong!', 'Whoops.. Looks like something went wrong.')
        }
      })
    },

    // Method to delete item resource
    deleteItem (url, event = '') {
      this.isLoading = true
      axios.delete(url + this.formData.id).then(() => {
        this.isLoading = false
        this.$root.$emit(event)
        this.closeModal()
        this.toast('success', 'Success!', 'Item was deleted successfully!')
      }).catch((error) => {
        this.isLoading = false
        if (error.response.status === 422) {
          this.formErrors = error.response.data.errors
        } else {
          this.toast('danger', 'Something went wrong!', 'Whoops.. Looks like something went wrong.')
        }
      })
    }
  }
};
```
Now anytime I create a new modal I can simply import the ModalActions mixin and reuse whatever methods are necessary.

##Conclusion
As a developer, it's crucial that we follow the DRY Principle in order to keep our code clean and build more maintainable web applications. Vue mixins help us accomplish this by allowing us to reuse code in multiple locations.