DEV Community

Michael Thiessen
Michael Thiessen

Posted on • Updated on • Originally published at michaelnthiessen.com

🔥 Vue Tips #27: Controlled Props — or How to Override Internal State

This newsletter was sent out to my list on September 22, 2021. Sign up here to get emails like this each week!

Hi!

First, I need to say a huge THANK YOU to all 212 of you who bought Clean Components in the sale that ended last week.

Your support of my work is absolutely astonishing — it's what gives me the motivation to keep creating.

So, it's officially the first day of fall, at least in the northern hemisphere.

Everything seems to be pumpkin-spiced these days:

  • lattes from Starbucks
  • scented candles (why)
  • Cheerios (okay, I'm willing to try that one)

Please tell me this pumpkin obsession is just a North American thing?

I'll spare you from pumpkin-spiced tips though. These ones are just the regular kind 🎃

— Michael

🔥 Controlled Props — or How to Override Internal State

Here we have a simple Toggle component that can show or hide content:

<template>
  <Toggle title="Toggled Content">
    This content can be hidden by clicking on the toggle.
  </Toggle>
</template>
Enter fullscreen mode Exit fullscreen mode

It keeps track of its own open state internally right now.

But what if we want to override that internal state, but only some of the time?

To do this we have to dynamically switch between relying on props and events, and relying on internal state:

export default {
  name: 'Toggle',

  props: {
    title: {
      type: String,
      required: true,
    },
    hidden: {
      type: Boolean,
      // Must be set to `undefined` and not `false`
      default: undefined,
    }
  },

  data() {
    return {
      // Internal state
      _hidden: false,
    };
  },

  methods: {
    toggleHidden() {
      // Switch between emitting an event and toggling state
      if (this.hidden !== undefined) {
        this.$emit('toggle-hidden');
      } else {
        this._hidden = !this._hidden;
      }
    },
  },

  computed: {
    $hidden() {
      // Dynamically switch between state or prop
      return this.hidden !== undefined
        ? this.hidden
        : this._hidden;
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

In the Toggle component we now have to use the $hidden computed prop:

<template>
  <div>
    <div
      class="title"
      @click="toggleHidden"
    >
      {{ title }}
    </div>
    <slot v-if="$hidden" />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

You can check out a more detailed tutorial on my blog.

🔥 Computed Props in Your Template: v-memo

Vue 3.2 gives you fine-grained control over template re-rendering using v-memo:

<div v-memo="[varA, varB, varC]">
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

This works much the same as a computed prop does. An element with v-memo is only re-rendered when the array changes, but otherwise it caches (or memoizes) the result.

When it's used with v-for you can selectively re-render only the parts of a list that have changed:

<div
  v-for="item in list"
  :key="item.id"
  v-memo="[item.id === selected]"
>
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

Here, we only update the nodes that went selected to unselected, or vice versa. Much faster if you're dealing with extremely long lists!

But since Vue is already so efficient with re-renders, you shouldn't need to use v-memo very often.

It's definitely a useful tool to help you get more performance — when you really need it.

Check out the docs for v-memo.

🔗 (Sponsored) Come work with me at Vidyard

It's where I've learned all that I know about Vue.

We're rapidly growing our remote engineering team and we need intermediate and senior devs in Canada and the US.

Our stack is Vue + GraphQL + Rails (with React, Node, and some other things, too).

You'll get to work on tons of diverse and interesting projects. Some of the things I've worked on:

  • In-house component library — written in Vue, of couse
  • Analytics dashboard and pipeline — lots of data, charts, and microservices
  • That time we rewrote our entire frontend in Vue — yes, we're very committed to paying down tech debt
  • UI for simple video editing

It's also the best place to work (in my opinion):

  • In 4 years I've never had to rush to hit a deadline. No evenings, weekends, or stress-induced baldness.
  • Every dev has their own "sandbox" — a replica of our production environment, running in AWS, complete with hot reloading.
  • The fundamentals: continuous deployment, e2e testing, mentors that help you grow, a focus on doing things right, very few meetings to interrupt your flow, too many memes, never enough memes.

Apply now and get the best job you've ever had.

📜 I didn't know this about computed props

Computed props may not work exactly how you think they do.

That's what a lot of us are finding out after reading Thorsten's post on a common misconception around rendering and computed props.

It's not something you'll run into every day, but it's good to know about if you run into performance problems.

Read it here: When a computed property can be the wrong tool

💬 Working

The greatest performance improvement of all is when a system goes from not-working to working. — John Ousterhout

First, get it to work. Then get it to work well. Don't worry about how good the code is until it's working — that's what refactoring is for.

It's so much easier to transform bad code into good code if it's already working correctly.

🗞 News: Vue.js Amsterdam update

Unfortunately, Vue.js Amsterdam had to cancel their conference for this October due to COVID, but it's on for February 2022!

Here are some upcoming conferences:

🧠 Spaced-repetition: Where do you put shared state?

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.

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>
Enter fullscreen mode Exit fullscreen mode

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, when the components sharing state are in separate components, in separate folders, it's harder to see that this is the solution.

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 as well.

Exclusive tips and insights every week

Join 8135 other Vue devs and get exclusive tips and insights like these delivered straight to your inbox, every week.

You have great content in your emails. I seriously learn something from every one of them. — Titus Decali

Thanks for another beautiful tip 🙏 — Victor Onuoha

Loving these, and the spaced repetition — Mark Goldstein

Sign up here

Top comments (0)