N
Nuxtβ€’5mo ago
deetstrab

Reactively add/remove a class name to <body>, from within a component...

I have a component with a boolean ref. As long as myBool.value === true, I'd like to bind a class name to <body>. I've attempted to use the useMeta and useMetaSafe composables (useHead > bodyAttrs.class) but it doesn't change reactively. Any idea what the heck I'm missing? This is driving me nuts and the documentation makes it sound relatively simple.
29 Replies
deetstrab
deetstrabOPβ€’5mo ago
useHead works to set the initial value of title, bodyAttrs.class, etc -- but none of these properties appear to be reactive.
Cake
Cakeβ€’5mo ago
how are u using useHead
Inès
Inès‒5mo ago
You should use: useHead({ bodyAttrs: { class: 'YOUR-CLASS-NAME' } }) But indeed, as the doc says (https://nuxt.com/docs/api/composables/use-head), bodyAttrs is non-reactive...
xibman
xibmanβ€’5mo ago
@deetstrab try with a composable for the class like useHead({ bodyAttrs: { class: () => test === true ? 'clas-true' : 'class-false' } })
deetstrab
deetstrabOPβ€’5mo ago
It says it is reactive, though?
No description
deetstrab
deetstrabOPβ€’5mo ago
This is exactly what I'm doing and have tried all variants of this that I know to.
useHead({
bodyAttrs: {
// class: displayMobileNav.value ? 'navOpen' : '' // Didn't work
class: () => displayMobileNav.value ? 'navOpen' : '' // Works like a charm inside the anonymous function
}
})
useHead({
bodyAttrs: {
// class: displayMobileNav.value ? 'navOpen' : '' // Didn't work
class: () => displayMobileNav.value ? 'navOpen' : '' // Works like a charm inside the anonymous function
}
})
I need to understand why this needs to be a function to work? What would be a good resource to read to better grasp this concept? Thanks in advance! πŸ™‚
Zampa
Zampaβ€’5mo ago
Is there a reason it has to be body and not just a wrapper div in app.vue?
deetstrab
deetstrabOPβ€’5mo ago
Thank you all for your helpfulness and thoughtful replies As I see it, yes. I needed to disable scrolling of the website while my mobile nav is open. It's possible a wrapper div would have worked too - this is a pattern I've used in many mobile navs that I've built over the years, though.
Zampa
Zampaβ€’5mo ago
You could just have a global utility class:
body.noScroll {
overflow: hidden;
}
body.noScroll {
overflow: hidden;
}
and then you can just use a computed property or a watcher to apply/remove document.body.style.noScroll as needed?
Zampa
Zampaβ€’5mo ago
I do something similar for locking the body when a modal is in place:
No description
deetstrab
deetstrabOPβ€’5mo ago
Yes but isn't targeting the DOM nodes like this an anti-pattern in Vue/Nuxt? That's exactly how I do it in vanilla/es I just wanted to force myself to do it the Vue/Nuxt way
Zampa
Zampaβ€’5mo ago
it's just adding/removing a class - it's what Vue is doing to the DOM underneath its hood, too.
deetstrab
deetstrabOPβ€’5mo ago
well of course and I see it as that simple too, fwiw - I've just been taught that it's an antipattern. I didn't consider it for that reason, but you're absolutely right - it's a simple solution that works perfectly! I need to get a better understanding of why this only works within an anonymous function
Zampa
Zampaβ€’5mo ago
Did you try something like:
useHead({
bodyAttrs: {
class: computed(() => {
if (isMobileNavVisible.value) return 'menu-is-open';

return '';
}),
},
});
useHead({
bodyAttrs: {
class: computed(() => {
if (isMobileNavVisible.value) return 'menu-is-open';

return '';
}),
},
});
deetstrab
deetstrabOPβ€’5mo ago
no, I was (formerly) just using a ternary eval
Zampa
Zampaβ€’5mo ago
the "computed" part of that would be necessary
deetstrab
deetstrabOPβ€’5mo ago
I really appreciate your help and conversation πŸ™‚ thank you, my friend
Zampa
Zampaβ€’5mo ago
πŸ‘
harlan
harlanβ€’5mo ago
if you don't wrap with a function or computed the value gets resolved immediately, meaning you're providing a literal to useHead which it can't bind reactivity to alternatively you can avoid the function syntax if you provide the ref explicitely like so
useHead({
bodyAttrs: {
class: {
navOpen: displayMobileNav
}
}
})
useHead({
bodyAttrs: {
class: {
navOpen: displayMobileNav
}
}
})
this will work the same as the Vue template
FiveDigitLP
FiveDigitLPβ€’5mo ago
I hate to piggy-back on this, but at the same time I am dealing with something very similar. The primary difference being that I don't even have any sort of conditional for the class. My goal is to have a body class that shows up on some pages and not on others. I thought it would be simple enough if I set bodyAttrs differently between pages, but that doesn't seem to be doing it. I have tried both setting the value bodyAttrs: { class: } to a ref and adding an anonymous function as above, but the class still seems to stick between pages. Any ideas?? Here's what I have for my pricing page:
const bodyClasses = ref('background-glow')

useHead({
title: pageTitle,
meta: [
{ name: 'description', content: pageDescription }
],
bodyAttrs: {
class: () => bodyClasses.value
}
})
const bodyClasses = ref('background-glow')

useHead({
title: pageTitle,
meta: [
{ name: 'description', content: pageDescription }
],
bodyAttrs: {
class: () => bodyClasses.value
}
})
And here's another page:
const bodyClasses = ref('')

useHead({
titleTemplate: null,
title: pageTitle,
meta: [
{ name: 'description', content: pageDescription }
],
bodyAttrs: {
class: () => bodyClasses.value
}
})
const bodyClasses = ref('')

useHead({
titleTemplate: null,
title: pageTitle,
meta: [
{ name: 'description', content: pageDescription }
],
bodyAttrs: {
class: () => bodyClasses.value
}
})
The title and page description changes, so I'm not sure why the bodyAttrs doesn't.
Cake
Cakeβ€’5mo ago
try
useHead({
bodyAttrs:() => ({
class: bodyClasses.value
})
})
useHead({
bodyAttrs:() => ({
class: bodyClasses.value
})
})
FiveDigitLP
FiveDigitLPβ€’5mo ago
Thanks! That didn't seem to work. At this point, I think it might have something to do with the way I have my page routes set up. For some reason, I have a software.vue page set up in the root of my pages folder in addition to having the nested routes under /software. I remember reading in the documentation during the early stages of my project about this being an option, but I don't remember why exactly I chose this structure as I think it has caused more headaches than it's worth.
manniL
manniLβ€’5mo ago
this was fixed recently in unhead FYI
manniL
manniLβ€’5mo ago
GitHub
Body style background image does not update when theme changes Β· Is...
Environment Operating System: Darwin Node Version: v21.6.2 Nuxt Version: 3.11.1 CLI Version: 3.11.1 Nitro Version: 2.9.5 Package Manager: [email protected] Builder: - User Config: devtools, typescript, ...
FiveDigitLP
FiveDigitLPβ€’5mo ago
Wait. You mean to tell me this is a bug and I'm not crazy?? πŸ˜… I manually updated to the latest version and it still doesn't seem to work... 😫
manniL
manniLβ€’5mo ago
also cleared lock file and all? then please file a bug report if possible (similar to that one or easier)
harlan
harlanβ€’5mo ago
your best bet is to make a minimal reproduction in Stackblitz and we can either guide you in the right direction or fix the bug you've ran into afaik this shouldn't be an unhead bug in the latest
FiveDigitLP
FiveDigitLPβ€’5mo ago
I'm sorry for the ignorance, but what do you mean by this? The package-lock file??
deetstrab
deetstrabOPβ€’5mo ago
you can do a nuxi upgrade with the force param to HARD UPGRADE πŸ™‚ - give that a shot npx nuxi upgrade -f
Want results from more Discord servers?
Add your server