ChatGPTとGitHub Copilotを活用しながらWebアプリケーションを作る【Vue.jsでユーザー更新処理を実装】

この記事の続きです。

programming-gogogogo.hatenablog.com

今回はVue.jsの画面からユーザーデータの更新と削除処理を実装します。

すでにChatGPTに質問しながら作成済みのユーザー詳細表示画面のコードは以下のようになっています。

<template>
  <div>
    <h1>User Details</h1>
    <p><strong>Name:</strong> {{ user.name }}</p>
    <p><strong>Email:</strong> {{ user.email }}</p>
    <br>
    <button @click="editUser">Edit</button>
    <button @click="deleteUser">Delete</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {}
    }
  },
  mounted() {
    const userId = this.$route.params.id;
    const apiURL = `http://localhost:8000/api/users/${userId}/`;

    fetch(apiURL)
      .then(response => response.json())
      .then(data => {
        this.user = data;
      })
      .catch(error => console.error(error));
  },
  methods: {
    editUser() {
      // Navigate to the edit user page
      this.$router.push(`/users/${this.user.id}/edit`);
    },
    deleteUser() {
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;

      fetch(apiURL, {
        method: 'DELETE'
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        // Navigate back to the user list page
        this.$router.push('/users');
      })
      .catch(error => console.error(error));
    }
  }
}
</script>

mounted()の処理でユーザーデータを取得し画面に反映することはすでにできています。 更新処理の方は上記のコードだとさらに別のページに遷移する実装になっていますが、今回はユーザー詳細ページにテキストボックスを設置して、そこに入力した値で更新できるようにしたいと思います。 また、削除処理は上記のコードで想定通り動いており、該当のユーザーが削除された後ユーザー一覧画面に戻る挙動になっていました。

ここではGitHub Copilotからコードを提案してもらうために「ユーザーを編集するためのテキストボックス」みたいな感じでコメントしてみたところ、以下のコードが提案されました。

<template>
  <div>
    <h1>User Details</h1>
    <p><strong>Name:</strong> {{ user.name }}</p>
    <p><strong>Email:</strong> {{ user.email }}</p>
    <br>
    <hr>
    <!-- textbox for editing the user -->
    <div>
      <h1>Edit User</h1>
      <label for="name">Name:</label>
      <input type="text" id="name" v-model="user.name">
      <br><br>
      <label for="email">Email:</label>
      <input type="text" id="email" v-model="user.email">
      <br><br>
      <button @click="updateUser">Update User</button>
    <button @click="editUser">Edit</button>
    <button @click="deleteUser">Delete</button>
  </div>
</template>

閉じタグを足したり、編集ボタンはすでに作成済みなのでUpdate Userボタンは消したりして、以下のように修正しました。

<template>
  <div>
    <h1>User Details</h1>
    <p><strong>Name:</strong> {{ user.name }}</p>
    <p><strong>Email:</strong> {{ user.email }}</p>
    <br>
    <hr>
    <!-- textbox for editing the user -->
    <div>
      <h1>Edit User</h1>
      <label for="name">Name:</label>
      <input type="text" id="name" v-model="user.name">
      <br><br>
      <label for="email">Email:</label>
      <input type="text" id="email" v-model="user.email">
      <br><br>
    </div>
    <button @click="editUser">Edit</button>
    <button @click="deleteUser">Delete</button>
  </div>
</template>

上記のコードで編集用のテキストボックスを設置することができました。

ただ、テキストボックスに値を入力するとUser Detailsで表示されてる静的な値にも影響を与える問題がありました。

原因はおそらくテキストボックスの値をv-modelでuser.nameやuser.emailにバインドしているため、テキストボックスに入力した値がUser Detailsで使用しているuser.nameやuser.emailに反映されてしまっているからだと思います。

試しに、編集用のテキストボックスで入力した値をバインドするための変数をdata()に追加してみました。

export default {
  data() {
    return {
      user: {},
      editUserName: '',
      editUserEmail: ''
    }
  },

そして、テキストボックスのv-modelにバインドしている値を変更しました。

<label for="name">Name:</label>
<input type="text" id="name" v-model="editUserName">
<br><br>
<label for="email">Email:</label>
<input type="text" id="email" v-model="editUserEmail">

すると編集用のテキストボックスで入力した値がUser Detailsで反映されなくなりました。

この変更により画面表示時に編集用のテキストボックスには空文字が表示されるようになったのがちょっと微妙ですが、一旦このまま進めます。

編集リクエストをeditUser()で送信するためにsend the PUT request to the API and parameters are from Edit User formとコメントして、GitHub Copilotからコードを提案してもらいました。

editUser() {
      // send the PUT request to the API and parameters are from Edit User form
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;
      fetch(apiURL, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: this.editUserName,
          email: this.editUserEmail
        })
      })

      // Navigate to the same page after editing
      this.$router.push(`/users/${this.user.id}`);
    },

上記のコードで正常に更新処理は実行できましたが、更新後にユーザー詳細画面に遷移しませんでした。

おそらく原因はfetch()の処理が非同期になっていたため、this.$router.push(/users/${this.user.id});の処理が先に実行されてしまっているからだと思います。

試しに以下のようにeditUser()の処理を書き換えて、ログが出力される順番を確認してみました。

editUser() {
      // send the PUT request to the API and parameters are from Edit User form
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;
      fetch(apiURL, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: this.editUserName,
          email: this.editUserEmail
        })
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        console.log('fetch PUT executed.')
      }
      )

      // Navigate to the same page after editing
      this.$router.push(`/users/${this.user.id}`);
      console.log('router.push executed.')
    },

するとやっぱりrouter.push()の処理が先に実行されていることがわかりました。

そして、以下のように修正すればユーザー更新後ページを際読み込みできるかと思いましたがうまくいかず

editUser() {
      // send the PUT request to the API and parameters are from Edit User form
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;
      fetch(apiURL, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: this.editUserName,
          email: this.editUserEmail
        })
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        // Navigate to the same page
        this.$router.push(`/users/${this.user.id}`);
      }
      )
    },

refresh the pageのコメントでGitHub Copilotからコードを提案してもらったthis.$router.go();を使ってみるとユーザー更新処理の後に際読み込みできました。

editUser() {
      // send the PUT request to the API and parameters are from Edit User form
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;
      fetch(apiURL, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: this.editUserName,
          email: this.editUserEmail
        })
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        // refresh the page
        this.$router.go();
      }
      )
    },

ただ、関数実行後にページを再読み込みするという実装だと違和感を感じ、SPAの挙動として正しいのかよくわからないのでChatGPTに聞いてみました。

入力

When I use Vue.js, how can I update page after some updating function was executed and some of data is changed(updated). Should I refresh the page?

出力

No, you don't need to refresh the page to update data in Vue.js. Vue.js is a reactive framework, which means that it automatically updates the view (i.e., the part of the page that the user sees) when the data changes.

When you change the data in Vue.js, the framework automatically detects the changes and updates the view accordingly. This means that you can update the data in your Vue.js application and the changes will be reflected in the view without any additional code.

Here's a simple example:

Let's say you have a Vue.js component that displays a list of items. The component has a data property called "items", which is an array of objects representing the items.

<template>
  <div>
    <ul>
      <li v-for="item in items" :key="item.id">{{ item.name }}</li>
    </ul>
    <<button @click="updateItems">Update Items</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: "Item 1" },
        { id: 2, name: "Item 2" },
        { id: 3, name: "Item 3" }
      ]
    };
  },
  methods: {
    updateItems() {
      this.items[0].name = "New Item 1";
    }
  }
};
</script>

In this example, the component displays a list of items and a button to update the items. When the button is clicked, the "updateItems" method is called, which changes the name of the first item in the "items" array.

Because the "items" data property is reactive, Vue.js automatically updates the view to reflect the change. In this case, the first item in the list will be updated to display the new name "New Item 1".

So, to summarize, in Vue.js, you can update the data and the view will be automatically updated without needing to refresh the page.

なるほど、上記の例を見る限り、更新処理の後はページを読み込み直すという発想よりもVue.jsで持っているデータを更新する方が良い気がしてきました。

それを踏まえて以下のようにコードを直しました。

editUser() {
      // send the PUT request to the API and parameters are from Edit User form
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;
      fetch(apiURL, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: this.editUserName,
          email: this.editUserEmail
        })
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        this.user.name = this.editUserName
        this.user.email = this.editUserEmail
      }
      )
      .catch(error => console.error(error))
    }

ページを読み込み直すのではなくて、data()で定義しているuser.nameを編集用のeditUserNameで更新するようにしました。 上記のコードで想定通りページの更新ができるようになりました。

vue-app/src/components/UserDetails.vue全体では以下のようになりました。

<template>
  <div>
    <h1>User Details</h1>
    <p><strong>Name:</strong> {{ user.name }}</p>
    <p><strong>Email:</strong> {{ user.email }}</p>
    <br>
    <hr>
    <!-- textbox for editing the user -->
    <div>
      <h1>Edit User</h1>
      <label for="name">Name:</label>
      <input type="text" id="name" v-model="editUserName">
      <br><br>
      <label for="email">Email:</label>
      <input type="text" id="email" v-model="editUserEmail">
      <br><br>
    </div>
    <button @click="editUser">Edit</button>
    <button @click="deleteUser">Delete</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {},
      editUserName: '',
      editUserEmail: ''
    }
  },
  mounted() {
    const userId = this.$route.params.id;
    const apiURL = `http://localhost:8000/api/users/${userId}/`;

    fetch(apiURL)
      .then(response => response.json())
      .then(data => {
        this.user = data;
      })
      .catch(error => console.error(error));
  },
  methods: {
    editUser() {
      // send the PUT request to the API and parameters are from Edit User form
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;
      fetch(apiURL, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: this.editUserName,
          email: this.editUserEmail
        })
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        this.user.name = this.editUserName
        this.user.email = this.editUserEmail
      }
      )
      .catch(error => console.error(error))
    },
    deleteUser() {
      const userId = this.$route.params.id;
      const apiURL = `http://localhost:8000/api/users/${userId}/`;

      fetch(apiURL, {
        method: 'DELETE'
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        // Navigate back to the user list page
        this.$router.push('/users');
      })
      .catch(error => console.error(error));
    }
  }
}
</script>

簡易的な作りですが一応CRUD処理が実装できました。
自分で知見を持ってないとChatGPTなどで回答をもらえても良い実装に落とし込めないことを実感できました。フロントエンドについても引き続き勉強していきたいと思います。