🔥 (240) Forcing a Component to Update, Creating an If...Else Component, and where do you put shared state?

Read this on my blog

Hey there!

Another week, another newsletter.

Enjoy!

— Michael

🔥 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/

🔥 Creating an If...Else Component

Ever thought about making an If...Else component in Vue, despite having v-if, v-else, and v-else-if?

Here's a quirky experiment that explores this idea:

<If :val="mainCondition">    <template #true>Render if true</template>    <Else :if="false">Else if condition</Else>    <template #false>Otherwise render this</template>  </If>

This setup uses Compound Components, default and named slots, and even render functions to achieve a flexible If...Else logic.

The If component checks a condition and decides which slot (true, false, or Else) to render.

The Else component — a Compound Component — allows for an else if condition.

I have a detailed write up about this component on my blog.

Here's a simplified version for a cleaner API:

<If :val="condition">    <True>Truth</True>    <Else :if="elseCondition">Else slot</Else>    <False> What up false branch! </False>  </If>

This experiment is a fun way to dive deep into Vue's features like slots, reactivity, and component communication. While it might not replace the built-in directives, it's a great learning exercise and could inspire other creative component designs.

Check out the demo and maybe even try implementing your version: Vue If...Else Component Demo

Remember — experimenting with "weird" ideas is a fantastic way to deepen your understanding of Vue!

🔥 Where do you put shared state?

Let's say we have a Button component that toggles an Accordion open and closed by changing the variable isOpen.

But the Button component changes it's text between "Show" and "Hide" based on the same variable, isOpen:

// Parent.vue  <template>    <!-- Both components need access to `isOpen`  -->    <Button :is-open="isOpen" @click="toggleOpen" />    <Accordion :is-open="isOpen">      Some interesting content in here.    </Accordion>  </template>

These two sibling components (because they are beside each other) need access to the same state, so where do we put it?

Answer: The lowest common ancestor!

Which, in this case, is the parent of both components.

Because state only flows down through props, shared state must be in a common ancestor. And we also want to keep state as close as possible, so we put it in the lowest common ancestor.

While this example may seem obvious to some, it's harder to see that this is the solution when the components sharing state are in separate components, in different folders.

Note: we also want to co-locate state with the logic that modifies it, so we have to put the toggleOpen method in the parent.

📜 Start with the Interface

We want to first figure out how this new composable is going to be used. Once we do that, it's much easier to figure out the implementation.

In this article, I'll expand on what this means exactly, how you can do it, and then show you an example of this in practice.

Check it out here: Start with the Interface

📜 Data Fetching Basics in Nuxt

Nuxt offers a set of powerful built-in tools for handling data fetching.

It provides composable functions that make it easy to fetch data and automatically handle server-side rendering, client-side hydration, and error handling. This enables you to write clean and efficient code, ensuring an optimal user experience.

In this article we'll examine the different methods Nuxt gives us for data fetching.

Check it out here: Data Fetching Basics in Nuxt

💬 Comments

"Good code is its own best documentation. As you're about to add a comment, ask yourself, 'How can I improve the code so that this comment isn't needed?'" — Steve McConnell

🧠 Spaced-repetition: Dedupe fetches in Nuxt

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.

Since 3.9 we can control how Nuxt deduplicates fetches with the dedupe parameter:

useFetch('/api/menuItems', {    // Cancel the previous request and make a new request    dedupe: 'cancel'  });

The useFetch composable (and useAsyncData composable) will re-fetch data reactively as their parameters are updated. By default, they'll cancel the previous request and initiate a new one with the new parameters.

However, you can change this behaviour to instead defer to the existing request — while there is a pending request, no new requests will be made:

useFetch('/api/menuItems', {    // Keep the pending request and don't initiate a new one    dedupe: 'defer'  });

This gives us greater control over how our data is loaded and requests are made.

🔗 Want more Vue and Nuxt links?

Michael Hoffman curates a fantastic weekly newsletter with the best Vue and Nuxt links.

Sign up for it here.

p.s. I also have a bunch of products/courses:

Unsubscribe

评论

此博客中的热门博文

丁薛祥在“77国集团和中国”气候变化领导人峰会上的致辞(全文)

The magic of scoped slots in Vue ✨ (3/4)