2

im trying to use v-if to render 2 buttons in different containers on page, depending on screen size is there way how to change parent of items in @media or way how to use screen size in v-if condition?

i tried

<div v-if="window.innerWidth < 860">
<!-- Some code -->
</div>

but it throws me "TypeError: Cannot read properties of undefined (reading 'innerWidth')"

(when i use just media query, there appears bug with radio btns, when they needs 2 clicks to get :checked styles) when i tried @media, in fact there was 4 buttons, that binded to 2 variables

<!-- button code -->
<label>
  <input type="radio" name="month-season" id="month" value="month" v-model="monthSeason">
  <null-button>
     <img src="../assets/green-smile.png" alt="#" id="month-icon">
     text
  </null-button>
</label>


<!-- null-button -->
<div class="parent">
  <div class="container">
    <slot></slot>
  </div>
<div>

<!-- styles for button -->
<style>
    .month-season {
        text-align: right;

        label {
            .container {
                color: rgba(45, 206, 137, 1);
                background: white;
                img {
                    margin: 0 4px 0 0;
                }
            }
            input:checked + .parent .container {
                background: rgba(45, 206, 137, 1);
                color: white;
                img {
                    filter: brightness(1000%);
                }
            }
            input {
                appearance: none;
            }
        }
    }
</style>

Error message when I use window.innerWidth when using window.innerWidth

Reporter
  • 3,897
  • 5
  • 33
  • 47
Dart
  • 21
  • 3
  • A site note: https://meta.stackoverflow.com/questions/285551/why-should-i-not-upload-images-of-code-errors-when-asking-a-question – Reporter Jul 11 '23 at 12:01
  • 1
    Does this answer your question? [Vue JS - How to get window size whenever it changes](https://stackoverflow.com/questions/49380830/vue-js-how-to-get-window-size-whenever-it-changes) – Moritz Ringler Jul 11 '23 at 12:36
  • @MoritzRingler for vue3 i suggested a more modern approach. so it's not a duplicate. the issue you pointed is outdated – Alexander Nenashev Jul 11 '23 at 12:39
  • @AlexanderNenashev You are using the exact same method though, you are just using a different approach to check the width - which is not related to Vue or this question, right? – Moritz Ringler Jul 11 '23 at 12:43
  • @MoritzRingler the OP tagged vuejs3 and my answer is strongly Vue3 focused and made for Vue3 only, it's not generic – Alexander Nenashev Jul 11 '23 at 12:47

2 Answers2

0

Vue3 SFC Playground

All JS expression that you use in a template are executed in a so called template context. It contains all stuff declared in a component and does NOT contains JS globals like window and its props like alert. So you should use window outside a template or to add to app.config.globalProperties.

But adding window to the context doesn't solve you problem because JS expressions using it aren't reactive and your code would work only on initial component rendering.

So you should go reactive here. A common Vue3 approach would be using composables here.

We could use 2 different approaches here. Either using matchMedia or window.innerWidth as you tried.

With matchMedia you can go with any CSS media query you like.

So in the both cases you listen for appropriate events and update ref that is used further in a template.

If you listen on window don't forget to remove the listener when the component is unmounted otherwise you will have problems of ghost event handlers.

So your matchMedia composable:

import {ref} from 'vue';

export function useMatchMedia(query){

    const match = window.matchMedia(query);

    const isMatching = ref(match.matches);

    match.addEventListener('change', e => isMatching.value = e.matches);

    return isMatching;
}

Your window.innerWidth composable:

import {ref, onUnmounted} from 'vue';

export function useInnerWidth(query){

    const match = window.matchMedia(query);

    const width = ref(window.innerWidth);

    const syncWidth = () => width.value = window.innerWidth;

    window.addEventListener('resize', syncWidth);

    onUnmounted(() => window.removeEventListener('resize', syncWidth));

    return width;
}

And the usage:

<script setup>

import { useMatchMedia } from './useMatchMedia';
const smallScreen = useMatchMedia('(max-width: 859px)');

import { useInnerWidth } from './useInnerWidth';
const innerWidth = useInnerWidth();

</script>

<template>
  <h1 v-if="smallScreen">I'm a smaller screen</h1>
  <h1 v-else>I'm a bigger screen</h1>
  <p v-if="innerWidth < 860">Smaller screen width: {{innerWidth}}</p>
  <p v-else>Bigger screen width: {{innerWidth}}</p>
</template>
Alexander Nenashev
  • 8,775
  • 2
  • 6
  • 17
0

Maybe try using useWindowSize or useElementSize.

MrSpt
  • 499
  • 3
  • 16