smastrom / notivue

🔔 Powerful toast notification system for Vue and Nuxt.
https://notivue.smastrom.io
MIT License
632 stars 7 forks source link

Breaking changes with v1 #5

Closed peterbud closed 12 months ago

peterbud commented 1 year ago

Updating to v 1.0.2 results the following error: [Vue warn]: Extraneous non-props attributes (position, pause-on-hover) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

With version 0.9.4 it works, and no error is visible.

Was there any change between the two versions? Position and pause-on-hover is now not the props of the component?

I'm using nuxt 3.6.1

smastrom commented 1 year ago

Hi Peter. Yes, I released a new major version, therefore there are some breaking changes in the API.

I was planning on writing a migration guide, but I think the best thing to do is to read the new documentation and then adapt your code.

Regarding your issue, configuration props have been moved to plugin options, which in your case are now located in plugins/notivue.client.ts:

import { notivue } from 'notivue'

export default defineNuxtPlugin(({ vueApp }) => {
  vueApp.use(notivue, {
    pauseOnHover: true
    // ... Other options
  })
})

If you need to update the above options dynamically, use the useNotivue composable:

<script setup>
import { useNotivue } from 'notivue'

const config = useNotivue()

function updateConfig() {
  config.position.value = 'top-left'
  config.pauseOnTouch.value = true
}
</script>

In addition to that, there are new configuration options such as pauseOnTabChange and limit. The options prop, has been renamed to notifications and can be found in the configuration object as well.

But the most important change is that starting from v1.0.0 <Notivue /> exposes the notification item via v-slot making a hundred times easier using custom components:

<script setup>
import { Notivue, Notifications } from 'notivue'
</script>

<template>
  <Notivue v-slot="item">
    <Notifications :item="item" /> 
    <!-- Or use your component in place of Notifications -->
  </Notivue>
</template>

Please take a look at the new documentation, which contains all the information you need.

Of course, let me know if you need any further assistance.

peterbud commented 11 months ago

Hi @smastrom , many thanks for the help - I have successfully managed to update my custom notification component, and it works.

The only problem still is the exported types are not available:

image

Is there any chance to fix them?

Another question, which I cannot find in the new documentation was how to make a composable which renders a custom component?

There was something like this in the previous doc:

const methods = ['success', 'error', 'warning', 'info'] as const

  methods.forEach((method) => {
    customPush[method] = ({ props = {}, ...options }) =>
      push[method]({
        ...options,
        render: {
          component: () => Toast,
          props: ({ notivueProps }) => ({
            ...notivueProps,
            ...props,
          }),
        },
      })
  })

I'm not sure what is the designated way to pass all the props to the custom component? Maybe if you have an example included in the doc, that would be a great help I believe.

smastrom commented 11 months ago

Hi @peterbud, types are exported. The import name is NotivueSlot and not NotivueItem, my bad for not updating the documentation, which I did now.

Regarding the other question, as I told you in the previous answer, v1 brings breaking changes on how custom components are handled.

I'll try to write an example here, but you can find it on the docs section as well.

Starting from v1, the render property of the UserPushOptions interface (which is the object you pass as only parameter to push) doesn't exist anymore. Instead, custom components are passed as default slot inside <Notivue />. This keeps your custom components in a single place and is a way better DX as being more declarative.

app.vue

<script setup lang="ts">
import { Notivue } from 'notivue';

import Custom from './components/Custom.vue';
</script>

<template>
<ClientOnly
  <Notivue v-slot="item">
    <Custom :item="item" />
  </Notivue>
</template>

Out of curiosity, you can try to see the data that are exposed by serializing the notification item (NotivueSlot interface):

<template>
  <Notivue v-slot="item">
    <div> {{ JSON.stringify(item) }} </div>
  </Notivue>
</template>

This object contains the data needed to render the notification content.

Always starting from v1, UserPushOptions interface has now a props property which can be used to pass any kind of data you want to include in the notification object (NotivueSlot).

So let's say you call push with:

push.success({
   message: 'This is a success message',
   props: {
      customName: 'Giovanni',
      isCriticalToast: true,
   },
})

You can now get the value of item.props.customName inside your custom component along with item.message, item.type etc.

According to that, you can use the same props to render different components, for example:

<script setup>
import { Notivue, Notifications } from 'notivue'

import MyToast from './MyToast.vue'
import MyOtherToast from './MyOtherToast.vue'
</script>

<template>
<ClientOnly>
  <Notivue v-slot="item">
    <MyToast
      v-if="item.props.isCriticalToast" 
      :item="item"
    />
    <MyOtherToast
      v-else-if="item.props.isOtherToast" 
      :item="item"
    />
    <!-- Use default notifications -->
    <Notifications
      v-else 
      :item="item"
    />
  </Notivue>
 </ClientOnly>
</template>

So regarding your specific case, you don't need anymore to create a custom composable unless you really want to keep things more organized, for example to avoid passing always the same props:

import { usePush, type NotificationOptions } from 'notivue'

type MyCustomProps = {
   isMyCustomToast: boolean
   customerName: string
   // Other props...
}

type CustomPushOptions = Partial<NotificationOptions> & {
   props?: Partial<MyCustomProps>
}

const methods = ['success', 'error', 'warning', 'info'] as const

type CustomPush = {
   [K in (typeof methods)[number]]: (options: CustomPushOptions) => ReturnType<Push[K]>
}

export function useCustomPush() {
   const push = usePush()

   const customPush = {} as CustomPush

   methods.forEach((method) => {
      customPush[method] = ({ props = {}, ...options }) =>
         push[method]<MyCustomProps>({
            ...options, // Some dynamic options
            title: 'New Message Request', // Some default options
            props: {
               ...props, // Some dynamic props
               isMyCustomToast: true, // Some default props
            },
         })
   })

   return customPush
}

Then call and use it with the same, identical API:

const customPush = useCustomPush()

customPush.error({
   message: 'Something went wrong.',
   props: {
      customerName: 'Marco Aurelio',
   },
})

Let me know if everything's clear and if you need further assistance.

Simone

peterbud commented 11 months ago

Many thanks - perfect!!