The context is that I have a Button
component and a SquareButton
component that wraps it as a higher order component. The SquareButton
takes the same props as Button
, but includes an extra prop needed for a calculation. All the rest of the props should be passed down to the Button
. Like so (to simplify):
Button.vue
<script setup lang="ts">
export interface ButtonProps {
width?: number
height?: number
// Plus a bunch more. These all need to make it onto the button.
}
// Already I am losing reactivity by destructuring here.
// However, I need access to `rest`.
const { width, height, ...rest } = withDefaults(defineProps<ButtonProps>(), {
width: 150,
height: 75,
})
</script>
<template>
<button v-bind="rest">
<slot />
<button>
</template>
<style scoped>
button {
display: flex;
width: v-bind(width + "px");
height: v-bind(height + "px");
}
</style>
SquareButton.vue
<script setup lang="ts">
import Button from "./Button.vue"
export interface SquareButtonProps extends ButtonProps {
size?: number
}
const { size, ...rest } = withDefaults(
defineProps<SquareButtonProps>(),
{
size: 30,
}
)
</script>
<template>
<Button :width="size" :height="size" v-bind="rest">
<slot />
</Button>
</template>
So the first problem is that I don't want to lose reactivity. If one of these props changes, I need everything to update. This means destructuring is bad. Various solutions to this problem recommend using the toRefs
function. So let's refactor to use this technique:
SquareButton.vue refactored
<script setup lang="ts">
import Button from "./Button.vue"
import { toRefs } from "vue"
export interface SquareButtonProps extends ButtonProps {
size?: number
}
const props = withDefaults(
defineProps<SquareButtonProps>(),
{
size: 30,
}
)
const { size, ...rest } = toRefs(props)
</script>
<template>
<!-- Error -->
<Button :width="size" :height="size" v-bind="rest">
<slot />
</Button>
</template>
The error I get here occurs because Button
is expecting props with normal value types like strings and numbers. But instead, I'm now giving it reactive objects, and it is rightly angry.
So how in the world am I supposed to forward these props down without losing reactivity? The reactivity is important because, when props change, I need my components to update.
<script setup lang="ts">
import Button from "./Button.vue"
export interface SquareButtonProps extends ButtonProps {
size?: number
}
const props = withDefaults(
defineProps<SquareButtonProps>(),
{
size: 30,
}
)
</script>
<template>
<Button :width="size" :height="size">
<slot />
</Button>
</template>