How to bubble up event in Composition Api Vue 3

The naive way to do it…

Child component (event bubbled from)

<!-- ChildComponent.vue -->
<template>
  <button @click="handleClick">Click Me!</button>
</template>

<script setup>
import { defineEmits } from 'vue'

const emits = defineEmits(['clicked'])

function handleClick() {
  emits('clicked', 'Some data')
}
</script>

Intermediate component

<!-- IntermediateComponent.vue -->
<template>
  <ChildComponent @clicked="handleClicked" />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'
import { defineEmits } from 'vue'

const emits = defineEmits(['clicked'])

function handleClicked(data) {
  // Bubble up the event to the parent of the WrapperComponent
  emits('clicked', data)
}
</script>

Parent component

<!-- ParentComponent.vue -->
<template>
  <div>
    <IntermediateComponent @clicked="onChildClicked" />
  </div>
</template>

<script setup>
function onChildClicked(data) {
  console.log('Event data from child:', data)
}
</script>

The more straightforward way to do it

<!-- IntermediateComponent.vue -->
<template>
  <ChildComponent v-bind="$attrs" @update:modelValue="handleModelUpdate" />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'
import { defineEmits } from 'vue'

// Emit function that captures all event names
const emits = defineEmits()

function handleModelUpdate(value) {
  emits('update:modelValue', value)
}
</script>

Jointure implementation in Typescript

Sometimes when you want to manage your code efficiency you may need to optimize some Java implementations.

Jointure implementation in Typescript may be such a case.

Underneath you will find my proposal to implement a jointure in Typescript environment. This is a generic code and may be optimized in many way for your implementation needs.

class ArrayExtensions {

    static innerJoin<TL, TR, TK, TJ>(lefts: TL[], rights: TR[], onLeftKey: (l: TL) => TK, onRightKey: (r: TR) => TK, joinBuilder: (l: TL, r: TR) => TJ): TJ[] {
        const leftsWithKeys = lefts.map(l => [l, onLeftKey(l)] as [TL, TK]);
        const rightsWithKeys = rights.map(r => [r, onRightKey(r)] as [TR, TK]);
        const rightGroupsByKey = rightsWithKeys.reduce((acc, [r, key]) => {
            if (!acc[key]) acc[key] = [];
            acc[key].push(r);
            return acc;
        }, {} as Record<TK, TR[]>);
        const result = leftsWithKeys.flatMap(([l, key]) => {
            const joineds = rightGroupsByKey[key] || [];
            return joineds.map(joined => joinBuilder(l, joined));
        });
        return result;
    }

    static leftJoin<TL, TR, TK, TJ>(lefts: TL[], rights: TR[], onLeftKey: (l: TL) => TK, onRightKey: (r: TR) => TK, joinBuilder: (l: TL, r: TR | null) => TJ, defaultRight?: TR): TJ[] {
        const leftsWithKeys = lefts.map(l => [l, onLeftKey(l)] as [TL, TK]);
        const rightsWithKeys = rights.map(r => [r, onRightKey(r)] as [TR, TK]);
        const rightGroupsByKey = rightsWithKeys.reduce((acc, [r, key]) => {
            if (!acc[key]) acc[key] = [];
            acc[key]. push(r);
            return acc;
        }, {} as Record<TK, TR[]>);
        const result = leftsWithKeys.flatMap(([l, key]) => {
            const joineds = rightGroupsByKey[key] || (defaultRight ? [defaultRight] : []);
            return joineds.map(joined => joinBuilder(l, joined));
        });
        return result;
    }

    static fullJoin<TL, TR, TK>(lefts: TL[], rights: TR[], leftKeyFunc: (l: TL) => TK, rightKeyFunc: (r: TR) => TK, defaultLeft?: TL, defaultRight?: TR): [TL | undefined, TR | undefined][] {
        const leftJoinedToRight = this.leftJoin(lefts, rights, leftKeyFunc, rightKeyFunc, (l, r) => [l, r] as [TL, TR | undefined], defaultRight);
        const joinedRightKeys = new Set<TK>(leftJoinedToRight.filter(m => m[1] !== undefined).map(m => rightKeyFunc(m[1]!)));
        const defaultLeftAndMissingRights = rights
            .map(r => [r, rightKeyFunc(r)] as [TR, TK])
            .filter(([_, key]) => !joinedRightKeys.has(key))
            .map(([r, _]) => [defaultLeft, r] as [TL | undefined, TR]);
        const fullJointure = leftJoinedToRight.concat(defaultLeftAndMissingRights);
        return fullJointure;
    }
}

Underneath the related unit tests

import { ArrayExtensions } from './ArrayExtensions';
// adjust the import path as necessary

describe('ArrayExtensions', () => {
    describe('innerJoin', () => {
        it('should correctly perform an inner join on two arrays', () => {
            const lefts = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
            const rights = [{ employeeId: 1, department: "Engineering" }, { employeeId: 2, department: "Human Resources" }];
            const result = ArrayExtensions.innerJoin(
                lefts,
                rights,
                l => l.id,
                r => r.employeeId,
                (l, r) => ({ name: l.name, department: r.department })
            );
            expect(result).toEqual([
                { name: "Alice", department: "Engineering" },
                { name: "Bob", department: "Human Resources" }
            ]);
        });
    });

    describe('leftJoin', () => {
        it('should correctly perform a left join on two arrays', () => {
            const lefts = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }, { id: 3, name: "Charlie" }];
            const rights = [{ employeeId: 1, department: "Engineering" }, { employeeId: 2, department: "Human Resources" }];
            const result = ArrayExtensions.leftJoin(
                lefts,
                rights,
                l => l.id,
                r => r.employeeId,
                (l, r) => ({ name: l.name, department: r ? r.department : "None" })
            );
            expect(result).toEqual([
                { name: "Alice", department: "Engineering" },
                { name: "Bob", department: "Human Resources" },
                { name: "Charlie", department: "None" }
            ]);
        });
    });

    describe('fullJoin', () => {
        it('should correctly perform a full join on two arrays', () => {
            const lefts = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
            const rights = [{ employeeId: 2, department: "Human Resources" }, { employeeId: 3, department: "Marketing" }];
            const result = ArrayExtensions.fullJoin(
                lefts,
                rights,
                l => l.id,
                r => r.employeeId,
                { id: 0, name: "Unknown" },
                { employeeId: 0, department: "None" }
            );
            expect(result).toEqual([
                [{ id: 1, name: "Alice" }, undefined],
                [{ id: 2, name: "Bob" }, { employeeId: 2, department: "Human Resources" }],
                [undefined, { employeeId: 3, department: "Marketing" }]
            ]);
        });
    });
});