useBreakpoint and useMediaQuery cause "Hydration text mismatch" on Nuxt
Describe the bug
This bug can be related to #912
It seems like useWindowSize, we need to use the tryOnMounted function also for useMediaQuery, to prevent the mismatch error.
Reproduction
https://stackblitz.com/edit/nuxt-starter-4j78pj
System Info
Doesn't matter.
Used Package Manager
npm
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Make sure this is a VueUse issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to https://github.com/vuejs/core instead.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion.
- [X] The provided reproduction is a minimal reproducible example of the bug.
struggling with the same issue 🤕
Same
same issue
Same issue, maybe vue.js 3.5 data-allow-mismatch can solve it,
The most common case is that a class is set with useMediaQuery ref variable, so we can write code like this:
<div
data-allow-mismatch="class"
:class="isLargeScreen && 'largeScreen'"
>
lorem...
</div>
Up to now, I've noticed that the latest version(v3.13.1) of Nuxt has already supported vue 3.5, I would like to try it later.
For now I rolled out my own composable to deal with this, until hydration it uses a fixed screen size of 768px (because we don't get this info on server) and during hydration it will go back to using the real useBreakPoints
import { createSharedComposable, tryOnMounted, useBreakpoints as useBreakpointsVueUse } from '@vueuse/core';
import breakpoints from '~/breakpoints';
import { MaybeRefOrGetter } from '@vueuse/shared';
export const useBreakpoints = createSharedComposable(() => {
type K = keyof breakpoints;
const ssrSize = 768;
const mounted = ref<boolean>(false);
const realBreakpoints = useBreakpointsVueUse(breakpoints);
tryOnMounted(() => (mounted.value = true));
const getValue = (point: MaybeRefOrGetter<K>) => Number.parseInt(breakpoints[unref(point)].replace('px', ''));
const proxyComputed = <Signature>(realMethod: Signature, fakeMethod: Signature): ReturnType<Signature> => {
return (...args: Parameters<Signature>) => {
const real = realMethod(...args);
return computed(() => (mounted.value ? real.value : fakeMethod(...args)));
};
};
return {
greaterOrEqual: proxyComputed(realBreakpoints.greaterOrEqual, (k) => ssrSize >= getValue(k)),
smallerOrEqual: proxyComputed(realBreakpoints.smallerOrEqual, (k) => ssrSize <= getValue(k)),
greater: proxyComputed(realBreakpoints.greater, (k) => ssrSize > getValue(k)),
smaller: proxyComputed(realBreakpoints.smaller, (k) => ssrSize < getValue(k)),
between: proxyComputed(realBreakpoints.between, (a, b) => {
return computed(() => ssrSize >= getValue(a) && ssrSize < getValue(b));
}),
isGreater(k: MaybeRefOrGetter<K>) {
return this.greater(k).value;
},
isGreaterOrEqual(k: MaybeRefOrGetter<K>) {
return this.greaterOrEqual(k).value;
},
isSmaller(k: MaybeRefOrGetter<K>) {
return this.smaller(k).value;
},
isSmallerOrEqual(k: MaybeRefOrGetter<K>) {
return this.smallerOrEqual(k).value;
},
isInBetween(a: MaybeRefOrGetter<K>, b: MaybeRefOrGetter<K>) {
return this.between(a, b).value;
},
current: proxyComputed(realBreakpoints.current, () =>
mountedObject.keys(breakpoints).filter((key) => getValue(key) >= ssrSize),
),
active() {
const bps = this.current();
return computed(() => (bps.value.length === 0 ? '' : bps.value.at(-1)));
},
};
});
The problem is that vueuse has to work for both ssr and non ssr and this solution will probably cause a screen jump if the final client size is not the ssrSize, this means that for people without SSR this will also happen because it will cause a rerender onMounted, so to fix this properly in vueuse we need to be able to detect if SSR is enabled or a flag in the composable that will set mounted to true straight away
I might have encountered a similar issue.
I'm using Nuxt3 + VueUse's useMediaQuery, and using useMediaQuery to determine the value of a class, but even when isLargeScreen is true, the correct dynamic styling isn't applied.
This is my minimal reproduction.