Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

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
    }
}
userVoice