Vue User Manager - Part 3

Part 3 - A User Details Component

In this part, we'll add a component that not only displays the details of a user, but also allows you to edit a user's details.

Add this component, just after the user-list component and before the app is mounted:

// UserForm component
app.component("user-form", {
    props: {
    	userId: {
    		type: Number
    	}
    },
    data(){
    	return {
    		firstName:"",
    		lastName:"",
    		email:""
    	}
    },
    template: `
        <div class="user-form-container">
        	<h2>User Details</h2>
            <form @submit.prevent="handleSubmit">
				<div>
				    <label>First Name:</label>
				    <input v-model="firstName" />
                </div>
                <div>
				    <label>Last Name:</label>
				    <input v-model="lastName" />
                </div>
                <div>
				    <label>Email:</label>
				    <input v-model="email" />
                </div>
                <div>
				    <input type="submit" id="btnSubmit" name="submit button">
				</div>
			</form>
		</div>`,
    mounted(){
    	// if the userId prop was passed in, then get the user for that ID
    	if(this.userId){
    	   	const user = uda.getUserById(this.userId);
	    	// initialize all the data members declared for this component
	    	this.firstName = user.firstName;
	    	this.lastName = user.lastName;
	    	this.email = user.email;
    	}
    },
    methods:{
    	handleSubmit(){
    		console.log("TODO: validate the input!");
    		console.log(this.userId, this.firstName, this.lastName, this.email);
    	}
    }
});

The user-form component displays a form for editing a user (we'll also use it for creating new users later). It declares a prop named userId, which indicates that the parent of this component will pass in the ID of the user whose details should be displayed in the form. We'll use this id by passing it into the user data access component's getUserById() method. This is done in the mounted() method, which we'll discuss in a moment.

The data() method defines the data that this component will work with. It will display the firstName, lastName, and email address of the user. Each one of these pieces of information will be displayed in INPUT elements that are in the UI of the component.

Now look at the template, which defines the UI for this component. All of the INPUT elements (except for the submit button) use Vue's v-model directive to bind the the data members to the form.

The v-model directive creates two-way binding between the INPUT element and a data member. This means that if the end user changes the value of an input that is using the v-model directive, the data member bound by v-model directive will automatically be updated by Vue. Likewise, if the data member's value changes, then the INPUT element's value will automatically be updated to reflect that the prop has changed. This is 2-way data binding, also known as 'reactivity'.

The mounted() method is one that you can add to any Vue component. Vue will call this method when the component appears on the screen (when the component is 'mounted' into the DOM). This is known as a 'life-cycle' method, and there are other life-cycle methods that you can make use of. In this case, we are taking the userId prop and using it to fetch all the user object that matches it (if you are referring to a prop or a data member in a component you must prefix it with .this). Once we get the user, we assign it's properties to our data members. As soon as the data members are updated, the UI should display their new values because of the v-model directive that is being used on the INPUT elements.

When the form is submitted, the handleSubmit() method is triggered, but notice that the event name (@submit) is followed by .prevent. This is a Vue mechanism that indicates that the form should not send the user input to a server when it is submitted (it will prevent the default behavior of a submit event). Vue apps are single page apps (SPAs), so the entire app is contained within a single page.

Now that we've defined the user-form, we'll add it to the template of the rootComponent like so (you can put it just unnder the user-list element):

<user-form v-if="selectedUserId" :userId="selectedUserId" />

Here the Vue v-if directive is used to display the user-form only if the selectedUser data member (of the rootComponent) is truthy. (if there is no selected user, then there is nothing to display in the user-form).

The selectedUser of the rootComponent is also passed to the user prop of the user-form.

Run the app and click on a user from the user-list, the selected user should appear in the user-form.

But if you click on another user from the list, you'll notice that the user-form remains unchanged. This is an example of how the 'reactivity' in Vue is not perfect. Let's walk through what's happenning here, and then we'll fix the problem.

  1. When you click on an LI in the user-list, it emits a 'user-selected' event and passes the user object that is bound to the LI.
  2. The root component is listening for 'user-selected' events and will trigger the handleUserSelected() method.
  3. The handleUserSelected() method is passed the selected user object and updates it's selectedUserId data member to reflect the change.
  4. The selectedUserId is bound to the user-form (by using it to set the userId prop), so if the selectedUserId changes, the Vue should automatically update the user on display in the user-form.

But the final step is not happening each time, in other words the user-form is not properly 'reacting' to the change in it's userId prop. In this case the issue is that the user-form has already been mounted, so the mounted() method is not re-running (the mounted() method contains code to fetch the user to display).

If your component is not updating automatically (it's not being 'reactive'), one fix might be to add a :key directive/attribute. Update the user-form element (in the template of the rootComponent) like so:

<user-form v-if="selectedUserId" :userId="selectedUserId" :key="selectedUserId" />

By binding the selectedUserId to the key attribute, it will force the component to re-render itself whenever the selectedUserId changes. When a component renders itself (appears on the screen), Vue will trigger the mounted() method.

Now run the app, select a user or two from the list, and then change the firstName, lastName and email by updating the INPUT elements. Then press the submit button. This will trigger the handleSubmit() method, which logs the values of the data members. Note that the data members are automatically updated when you change the value of an INPUT, and this is because we've used the v-model directive to set up 2-way data-binding for each of the data members.

In the next step we'll have the user-form emit an event up to it's parent (the root component) so that the parent can be notified that the form has been submitted.