Exclusive tips every week

Join 11,067 other Vue devs and get exclusive tips and insights delivered straight to your inbox, every week.

    Picture of Michael Thiessen

    👋Hey friend! I work hard to send you amazing stuff each week.

    — Michael

    I really love and enjoy reading these emails.

    You are one of the most pro VueJS devs I know, and I am happy that you share this knowledge.

    Fabian Beer

    Here's my latest newsletter

    🔥 (#118) Composable Return Values

    What's up!

    I made a mistake in the last newsletter (thanks to Michael Hoffman for pointing this out, he also has a great Vue newsletter).

    After speaking with Eduardo (creator of Vue Router) and testing some things on StackBlitz, I figured out where the gap in my understanding was.

    Last week, I said that useRoute isn't reactive, which is incorrect.

    What I meant is that, if you want to trigger something on a route change, you can't put a watcher on the route object returned from it. This route object is, in fact, a reactive object — it's created using the reactive method.

    But if you look at this small demo, you'll see that when you change routes, you have to watch the path returned from it, and not the route object itself.

    Yes, a very subtle distinction, but it clearly makes a big difference. I hope this clears things up — and that you learned a bit more about Vue Router in the process!

    Here's the updated tip:

    It took me way too long to figure this one out, but here it is:

    // Doesn't change when route changes
    const route = useRoute();
    
    // Changes when route changes
    const path = useRoute().path;

    If we need the full route object in a reactive way, we can do this:

    // Doesn't change when route changes
    const route = useRoute();
    
    // Changes when route changes
    const route = useRouter().currentRoute.value;

    Since Nuxt uses Vue Router internally, this works equally well in Nuxt and vanilla Vue apps.

    Here's a demo to see this for yourself: Demo

    — Michael

    🔥 Composable Return Values

    When you're creating composables, here are some things you can do to make it easier to work with their return values.

    Return an object of refs, so that reactivity is preserved when destructuring:

    // Composable
    const useBurger = () => {
      const lettuce = ref(true);
      const ketchup = ref(true);
    
      return {
        lettuce,
        ketchup,
      };
    };
    // Component
    setup() {
      // We can destructure but still keep our reactivity
      const { ketchup } = useBurger();
    
      watchEffect(() => console.log(ketchup.value));
    
      return {
        ketchup,
        removeKetchup: () => ketchup.value = false
      };
    },

    If you don't want to destructure the values, you can always wrap it in reactive and it will be converted to a reactive object:

    // Component
    setup() {
      // Wrap in `reactive` to make it a reactive object
      const burger = reactive(useBurger());
    
      watchEffect(() => console.log(burger.ketchup));
    
      return {
        burger,
        removeKetchup: () => burger.ketchup = false
      };
    },

    One great thing VueUse does is return a single value by default. If you happen to need more granular control, you can pass in an option to get an object returned instead:

    import { useTimestamp } from '@vueuse/core';
    
    // Most of the time you only need the timestamp value
    const timestamp = useTimestamp();
    
    // But sometimes you may need more control
    const {
      timestamp,
      pause,
      resume,
    } = useTimestamp({ controls: true });

    I think presenting different interfaces based on how you need to use the composable is a brilliant pattern. This makes it simpler to work with while not sacrificing any precise control.

    🔥 Forcing a Component to Update

    What do you do if a component isn’t updating the way it should?

    Likely, this is caused by a misunderstanding and misuse of the reactivity system.

    But let’s look at a quick solution using forceUpdate:

    import { getCurrentInstance } from 'vue';
    
    const methodThatForcesUpdate = () => {
      // ...
      const instance = getCurrentInstance();
      instance.proxy.forceUpdate();
      // ...
    };

    Using the Options API instead:

    export default {
      methods: {
        methodThatForcesUpdate() {
          // ...
          this.$forceUpdate();  // Notice we have to use a $ here
          // ...
        }
      }
    }

    Now, here comes the sledgehammer if the previous approach doesn’t work.

    I do not recommend using this approach. However, sometimes you just need to get your code to work so you can ship and move on.

    But please, if you do this, keep in mind this is almost always the wrong way, and you’re adding tech debt in to your project.

    We can update a componentKey in order to force Vue to destroy and re-render a component:

    <template>
      <MyComponent :key="componentKey" />
    </template>
    
    <script setup>
    import { ref } from 'vue';
    const componentKey = ref(0);
    
    const forceRerender = () => {
      componentKey.value += 1;
    };
    </script>

    The process is similar with the Options API:

    export default {
      data() {
        return {
          componentKey: 0,
        };
      },
      methods: {
        forceRerender() {
          this.componentKey += 1;
        }
      }
    }

    You can find a deeper explanation here: https://michaelnthiessen.com/force-re-render/

    🔥 Teleportation

    You can get an element to render anywhere in the DOM with the teleport component in Vue 3:

    <template>
      <div>
        <div>
          <div>
            <teleport to="body">
              <footer>
                This is the very last element on the page
              </footer>
            </teleport>
          </div>
        </div>
      </div>
    </template>

    This will render the footer at the very end of the document body:

    <html>
      <head><!-- ... --></head>
      <body>
        <div>
          <div>
            <div>
              <!-- Footer element was moved from here... -->
            </div>
          </div>
        </div>
        <!-- ...and placed here -->
        <footer>This is the very last element on the page</footer>
      </body>
    </html>

    This is very useful when the logic and state are in one place, but they should be rendered in a different location.

    One typical example is a notification (sometimes called a toast).

    We want to be able to display notifications from wherever inside of our app. But the notifications should be placed at the end of the DOM so they can appear on top of the page:

    <!-- DogList.vue -->
    <template>
      <div>
        <DogCard
          v-if="dogs.length > 0"
          v-for="dog in dogs"
          :key="dog.id"
          v-bind="dog"
        />
        <teleport to="#toasts">
          <!-- Show an error notification if we have an error -->
          <Toast
            v-if="error"
            message="Ah shoot! We couldn't load all the doggos"
          >
        </teleport>
      </div>
    </template>

    Which will render this to the DOM:

    <html>
      <head><!-- ... --></head>
      <body>
        <div id="#app">
          <!-- Where our Vue app is normally mounted -->
        </div>
        <div id="toasts">
          <!-- All the notifications are rendered here,
               which makes positioning them much easier -->
        </div>
      </body>
    </html>

    Here's the complete documentation: https://vuejs.org/api/built-in-components.html#teleport

    📜 Suspense: Everything You Need to Know

    I wrote this article for VueSchool.io to clear up some misconceptions I've seen around Suspense.

    If you load data in your application, I think you'll find it useful.

    There are even some code demos so you can code along with the article!

    Check it out here: Suspense: Everything You Need to Know

    💬 Carpentry vs. Software

    "In carpentry you measure twice and cut once. In software development you never measure and make cuts until you run out of time." — Adam Morse

    🧠 Spaced-repetition: Mixing local and global styles together

    The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨‍🔬

    Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.

    Normally, when working with styles we want them to be scoped to a single component:

    <style scoped>
      .component {
        background: green;
      }
    </style>

    In a pinch though, you can also add a non-scoped style block to add in global styles if you need it:

    <style>
      /* Applied globally */
      .component p {
        margin-bottom: 16px;
      }
    </style>
    
    <style scoped>
      /* Scoped to this specific component */
      .component {
        background: green;
      }
    </style>

    Be careful, though — global styles are dangerous and hard to track down. Sometimes, though, they're the perfect escape hatch and precisely what you need.



    p.s. I also have four courses: Vue Tips Collection, Mastering Nuxt 3, Reusable Components and Clean Components

    Here's what others are saying

    I'm starting to think that your newsletter is one of the best things that happened to me this year. I just love these emails.
    Stanislaw Gregor
    I'm somewhere in the upper-middle level at Vue, and I never miss an email you and always find something cool when reading!
    Eduard Climov
    This is the first time where I'm actually enjoying email newsletters. I like your format a lot.
    Fahmi Alfituri
    You have great content in your emails. I seriously learn something from every one of them.
    Titus Decali
    Just wanted to say I enjoy these emails. They encourage me to constantly improve my craft. Fantastic work.
    Joe Radman
    Thanks for another beautiful tip 🙏
    Victor Martins Onuoha
    Loving these, and the spaced repetition.
    Mark Goldstein
    I really enjoy reading your emails, because I love Vue. Thanks for these emails.
    Arturo Espinoza
    I really love and enjoy reading these emails. You are one of the most pro VueJS devs I know, and I am happy that you share this knowledge.
    Fabian Beer
    THANK YOU! I did not know about the deep property, so I assumed you simply couldn't watch objects.
    Darryl Noakes
    I really must say you are doing a really great job. Now I am aware of a cleaner and more performant way to use Tailwind. Thanks a lot!
    Henry Eze
    Thank you so much, I really enjoy and appreciate your emails.
    Carlos Gonzalez
    Thanks for sharing great Vue tips.
    Fernando Navarro
    I really enjoy these tips.
    Martin H
    Thank you so much for the weekly Vue education. Thanks and live on longer to educate us more.
    Kabolobari Benakole
    I look forward to your emails every week. This week was something I knew, but I like to be reminded of. Thanks for keeping it up!
    Nathan Strutz
    Thank you for your weekly emails. I always look forward to learning a new tip about Vue or at least relearning old tips :)
    Colin Johnson
    I have really been enjoying your weekly emails, and I also got my two team members to join in on the fun as well.
    Keith Dawson
    Thank you for your newsletter, your explanations have very concise examples and I like it.
    Nicolas Decayeux
    Thanks A LOT for your great work!!! One of the few newsletters that I let pass!
    Martin Tillmann
    🎉 Get 30% off Vue Tips Collection!
    Get it now!