The elements being displayed in the header (e.g. the Cancel and Next links) will also be conditionally shown based on the step value.

In the parent App component; let’s first introduce a step data property and set it to a value of 1 . Since we’ll be using the step property to dictate how elements in the phone-body section is shown, we’ll pass it down as props to the phone-body component.

<template>

<div id="app">

<div class="app-phone">

...

<phone-body

:step="step"

:posts="posts"

:filters="filters"

/>

...

</div>

</div>

</template> <script>

...

export default {

name: "App",

data() {

return {

step: 1,

posts,

filters

};

},

...

};

</script>

When the user goes through the submission process; we want the final outcome to involve submitting a new post to the homepage feed. In other words, we want the user to be able to push (i.e. introduce) a new post object to the collection of posts (i.e. the posts.js file). We’ll control the username and userImage properties but we’ll need to capture the rest from the user.

In total, we need to capture the selectedFilter the user wants to apply, the post image they’d like to upload, and the caption to the post. With these properties in mind, let’s declare these properties as empty initial values in the App component and pass them down as props to phone-body as well:

<template>

<div id="app">

<div class="app-phone">

...

<phone-body

:step="step"

:posts="posts"

:filters="filters"

:image="image"

:selectedFilter="selectedFilter"

v-model="caption"

/>

...

</div>

</div>

</template> <script>

... export default {

name: "App",

data() {

return {

step: 1,

posts,

filters,

image: "",

selectedFilter: "",

caption: ""

};

},

...

};

</script>

Notice how we’re using the v-model directive to bind the value of the caption prop? This is because we want to avoid directly mutating the parent caption data property from the child component. We’ll explain this in a little more detail near the end of the article.

With the main properties initialized, we’ll create the method responsible in directing the user from step 1 to step 2. The one action responsible for this will be to upload an image by clicking the upload icon in the phone footer:

In App.vue , we’ll create a new method labelled uploadImage() that’ll hold the responsibility of uploading the image and then directing the user to step 2.

<input> elements with type="file" allows users to choose one or more files from their device storage to upload. So in the footer section of the template in App.vue, we’ll create an input element (of type="file" ) that has a change event listener that calls an uploadImage() method when triggered:

<template>

<div id="app">

<div class="app-phone">

...

<div class="phone-footer">

...

<div class="upload-cta">

<input type="file"

name="file"

id="file"

class="inputfile"

@change="uploadImage"/>

<label for="file">

<i class="far fa-plus-square fa-lg"></i>

</label>

</div>

</div>

</div>

</div>

</template> <script>

...

export default {

name: "App",

...

};

</script> <style lang="scss" src="./styles/app.scss">

// Styles from stylesheet

</style>

Since we want the upload icon to be the action that involves uploading an image, we’ve set a visibility: hidden property to the class applied to the input element. In addition, we’ve wrapped a <label for="file"> element around the upload icon. When the user clicks the upload icon (i.e. the label element), it will be treated as if he/she is clicking the input element.

The input element has a change event listener that calls an uploadImage() method when triggered. We’ll create this accompanying uploadImage() method within a methods property in the <script> of App.vue . To upload images, we’ll use the FileReader API. Here’s the uploadImage() method in its entirety:

<template>

<div id="app">

...

</div>

</template> <script>

... export default {

name: "App",

...

methods: {

uploadImage(evt) {

const files = evt.target.files;

if (!files.length) return; const reader = new FileReader();

reader.readAsDataURL(files[0]);

reader.onload = evt => {

this.image = evt.target.result;

this.step = 2;

}; // To enable reuploading of same files in Chrome

document.querySelector("#file").value = "";

}

},

...

};

</script> <style lang="scss" src="./styles/app.scss">

// Styles from stylesheet

</style>

Let’s walk through what this method does.

When the user uploads an image, the event object has a list of file objects that can be accessed from evt.target.files . If no files exist, we return early. If files do exist, we continue by setting new FileReader() to a reader variable. FileReader() lets us asynchronously read the contents of the file object. We use the readAsDataUrl function from the reader variable to read the contents of the uploaded file (i.e. the first file object from evt.target.files ). When the file contents are being read with readAsDataUrl , the onload event handler gets triggered with which we use to set the component image property to the target.result of the event. We then set the component step value to 2. The Chrome browser does not fire a change event if we decide to upload the same image twice. To perform a small change to bypass this, we’ve directly set the value of the input field to a blank string at the end of the method. Now when the user attempts to re-upload the same file again; it will always be detected as a change event.

With all of the changes we’ve laid out above; the entire App.vue file will now look like the following:

At this moment, we should be able to upload image files successfully.

Note: For the sake of simplicity, we won’t set up functionality to restrict the user to not upload anything but certain image files. If the user uploads files that are not image-related (i.e. not .jpg, .png, .gif), no image will be shown during the submission process.

The homepage feed should only be displayed when the user is in the very first step. To ensure this, we can add a v-if statement on the feed DOM element in the PhoneBody.vue file to dictate that the feed should only be shown when the user is at step === 1 . To access the step prop that’s being passed in, we’ll define the prop in the components props property as we use it:

<template>

<div class="phone-body">

<div v-if="step === 1" class="feed">

<instagram-post v-for="post in posts"

:post="post"

:key="posts.indexOf(post)">

</instagram-post>

</div>

</div>

</template> <script>

...

export default {

name: "PhoneBody",

props: {

step: Number,

posts: Array,

filters: Array

},

...

};

</script> <style lang="scss" src="../styles/phone-body.scss">

// Styles from stylesheet

</style>

Now, the feed will only be shown when the user is at the first initial step.