MewTheDev
MewTheDev

Reputation: 21

How to pass data to child component using vue 3 h render after data is changed?

I have an issue that needs to be resolved.

a.ts


const open = ref<boolean>(false)
h(RowAction, {
  open: open,
  onDelete: () => {
    Data.remove().then(() => {}).catch(() => {}).finally(() => (open.value = false))
  }
})

RowAction.vue

<script lang="ts" setup>
defineProps<{open: boolean}>()
defineEmits<{ (e: 'delete'): void }>()
</script>

<template>
  <div>
    <button @click="$emit('delete')">
      delete
    </button>
  </div>
  <my-alert :open="open">
    <alert-tetx>test</alert-text>
    <button @click="open = false">close</button>
    <button @click="$emit('delete')">delete</button>
  </my-alert>
</template>

I want the delete event to run when clicked, and the alert to close after the promise resolves or rejects. However, I don't know why my code is not running.

Upvotes: 2

Views: 62

Answers (2)

Alexander Nenashev
Alexander Nenashev

Reputation: 23907

Given that events are props named onXXX you can define the delete event as a prop:

Playground

RowAction.vue

<script setup lang="ts" >

import {ref} from 'vue';

const props = defineProps<{
  onDelete: () => void | Promise<any>
}>();

const open = ref(false);
const error = ref('');
const disabled = ref(false);

async function onDelete(){
    error.value = '';
    disabled.value = true;
    try{
      await props.onDelete();
      close();
    }catch(e){
      error.value = e.message;
    }finally{
      disabled.value = false;
    }
}

function close(){
  open.value = false;
  error.value = '';
}

</script>

<template>
  <div>
    <button @click="open = true">
      delete
    </button>
    <dialog :open>
        <div> are you sure to delete ? </div>
        <div v-if="error" style="font-size: small;color: red;">{{ error }}</div>
        <button :disabled @click="close">close</button>
        <button :disabled @click="onDelete">delete</button>
    </dialog>
  </div>
</template>

App.vue

<script setup lang="ts">
import {h} from 'vue';
import RowAction from './RowAction.vue';

class Data{
  static remove(){
    return new Promise((resolve, reject) => {
          Math.random() > .5 ? 
            setTimeout(resolve, 1000) :
            setTimeout(() => reject(new Error('Operation has failed')), 1000);
        });
  }
}
const alert = msg=>window.alert(msg);
</script>

<template>
  <label>With promise work</label>
  <row-action @delete="Data.remove()"/>
  <label>Immediate</label>
  <row-action @delete="alert('deleted')"/>
</template>

Upvotes: 0

Alexander Nenashev
Alexander Nenashev

Reputation: 23907

Exposing and managing internal property open of RowAction could be not a perfect design pattern. Just provide a resolve/reject pair in the delete event to narrow scope of the problem to the event callback only:

Playground

RowAction.vue:

<script setup lang="ts" >

import {ref} from 'vue';
const open = ref(false);
const error = ref('');
const disabled = ref(false);
const $emit = defineEmits<{ (e: 'delete', defer: () => ReturnType<PromiseConstructor['withResolvers']>) }>()

function onDelete(){
    let needsResolving = false;
    error.value = '';
    $emit('delete', () => {
      needsResolving = true;
      disabled.value = true;
      const {promise, resolve, reject} = Promise.withResolvers();
      promise
        .then(() => open.value = false, e => error.value = e.message)
        .finally(() => disabled.value = false);
      return {promise, resolve, reject};
    });
    needsResolving || close();
}

function close(){
  open.value = false;
  error.value = '';
}

</script>

<template>
  <div>
    <button @click="open = true">
      delete
    </button>
    <dialog :open>
        <div> are you sure to delete ? </div>
        <div v-if="error" style="font-size: small;color: red;">{{ error }}</div>
        <button :disabled @click="close">close</button>
        <button :disabled @click="onDelete">delete</button>
    </dialog>
  </div>
</template>

App.vue

<script setup lang="ts">
import {h} from 'vue';
import RowAction from './RowAction.vue';

class Data{
  static remove(){
    return new Promise((resolve, reject) => {
          Math.random() > .5 ? 
            setTimeout(resolve, 1000) :
            setTimeout(() => reject(new Error('Operation has failed')), 1000);
        });
  }
}

function render(){
  return h(RowAction, {
    onDelete: async (defer) => {
      const {resolve, reject} = defer();
      Data.remove().then(resolve, reject);
    }
  });
}

</script>

<template>
  <label>With promise work</label>
  <render/>
  <label>Immediate</label>
  <row-action/>
</template>

Upvotes: 0

Related Questions