Making the Title Component Less Smart

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

In the last tutorial, we created a title component, which we're reusing so that we can have a consistent look and styling.

<template>
<div :class="$style.component">
<h1>
{{ categoryName }}
</h1>
</div>
</template>
<script>
export default {
name: 'Title',
... lines 12 - 32
};
</script>
<style lang="scss" module>
.component {
h1 {
font-size: 1.7rem;
}
}
</style>

Cool! In product-show.vue, let's use that! Import TitleComponent from @/components/title, add this into the components key, then up here, instead of our manual <h1>, say <title-component />.

<template>
<div>
<loading v-if="loading" />
<div v-if="product">
<title-component />
</div>
</div>
</template>
<script>
... lines 12 - 13
import TitleComponent from '@/components/title';
... line 15
export default {
... line 17
components: {
Loading,
TitleComponent,
},
... lines 22 - 40
};
</script>

I'm purposely not passing a prop to this yet... and yes, I know it looks weird - like "how will it know what title to render?". Ya see... we have a problem.

Back at the browser, we're on a product page and it... seems to... kind of work. It says "All Products". But more importantly, there's an error!

Missing required prop categories.

We're apparently supposed to pass a categories prop to title... which is weird, because I'm not sure what categories have to do with printing a title. We better go check out that component. Ah... this component is way too smart: it expects us to pass it the array of all categories and the currentCategoryId. And then it does the logic to figure out if we're on a category page and either prints that category name or "All Products". There's no way for us to make it render anything else.

<template>
<div :class="$style.component">
<h1>
{{ categoryName }}
</h1>
</div>
</template>
<script>
export default {
name: 'Title',
props: {
currentCategoryId: {
type: String,
default: null,
},
categories: {
type: Array,
required: true,
},
},
computed: {
categoryName() {
if (this.currentCategoryId === null) {
return 'All Products';
}
const category = this.categories.find((cat) => (cat['@id'] === this.currentCategoryId));
return category ? category.name : '';
},
},
};
</script>
... lines 35 - 43

Converting to a Dumb Component

What we need to do is convert title into a dumb component that does nothing more than receives props and uses them. This is really a mistake that I made in the last tutorial. We've talked a few times about having dumb components that mostly just render markup and then smart components that do calculations & load data, but don't render much markup. This is not an absolute rule... and I don't always follow it - but it's a nice guide to keep things organized and reusable.

Ok: let's make this component less smart! Under props, we only need one: call it text. It will be a String and also required.

... lines 1 - 8
<script>
export default {
name: 'Title',
props: {
text: {
type: String,
required: true,
},
},
... lines 18 - 28
};
</script>
... lines 31 - 39

Then, in the template, instead of categoryName, just render text!

<template>
<div :class="$style.component">
<h1>
{{ text }}
</h1>
</div>
</template>
... lines 8 - 39

What about all the logic inside the categoryName computed prop? Copy this and delete the entire computed section. Now open assets/components/catalog.vue. This is the one place that currently renders the title component and it is what should be responsible for determining its title text. Down in the component, this doesn't have a computed section yet, so add one after data - computed - and paste categoryName. Both this.currentCategoryId and this.categories are available on this component, so this "should" just work.

... lines 1 - 32
export default {
name: 'Catalog',
... lines 35 - 58
computed: {
categoryName() {
if (this.currentCategoryId === null) {
return 'All Products';
}
const category = this.categories.find((cat) => (cat['@id'] === this.currentCategoryId));
return category ? category.name : '';
},
},
... lines 70 - 105
};
</script>

Back up top, we can shorten <title-component> significantly: we only need to pass text set to categoryName.

<template>
<div>
<div class="row">
<div class="col-3">
<title-component :text="categoryName" />
</div>
... lines 7 - 9
</div>
... lines 11 - 19
</div>
</template>
... lines 22 - 105

Let's make sure this still works. Back at the browser, click "All Products". The title looks good! Try "Office Supplies" and... perfect!

Thanks to this, in product-show, we're free to pass whatever we want to the title, like :text="product.name".

<template>
<div>
<loading v-if="loading" />
<div v-if="product">
<title-component :text="product.name" />
</div>
</div>
</template>
... lines 10 - 43

I love that. And... it even works.

Next: let's bring this page completely to life with a full template and a nice, standalone color selector that we'll soon use to selected a product color before adding it to the cart.

Leave a comment!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@fortawesome/fontawesome-free": "^5.15.1", // 5.15.1
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.3
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.2.0
        "eslint-plugin-import": "^2.19.1", // 2.22.1
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.12
        "vue-loader": "^15.9.1", // 15.9.4
        "vue-template-compiler": "^2.6.11", // 2.6.12
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}