質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Vue.js

Vue.jsは、Webアプリケーションのインターフェースを構築するためのオープンソースJavaScriptフレームワークです。

Vuetify.js

Vuetify.jsは、マテリアルデザインを基本とするVue.jsのCSSフレームワークです。多くのマテリアルデザインのコンポーネントを提供しており、あらゆるアプリケーションに対応可能。vue-cli用テンプレートがあり、簡単にページを作成できます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

1回答

5160閲覧

Vue.js v-formでsubmitしても値が取得できない

mellan22

総合スコア4

Vue.js

Vue.jsは、Webアプリケーションのインターフェースを構築するためのオープンソースJavaScriptフレームワークです。

Vuetify.js

Vuetify.jsは、マテリアルデザインを基本とするVue.jsのCSSフレームワークです。多くのマテリアルデザインのコンポーネントを提供しており、あらゆるアプリケーションに対応可能。vue-cli用テンプレートがあり、簡単にページを作成できます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2020/08/25 12:31

編集2020/08/25 12:34

実現したいこと

  • リスト複数の親コンポーネント(新規入力・編集)で子コンポーネント(フォーム)を共有する
  • 親から子に初期データを渡す
  • 子で入力受け付けを行いsubmitと同時に、親にデータを渡す
  • 親でfirestore(データベース)にアクセスしてデータを追加・編集する

困っていること

  • submitしてもformの値が取得できない

参考にしている資料
https://katuo-ai.com/vuejs-avoid-mutating
https://qiita.com/harhogefoo/items/7232508db1f07e6b1859
https://b1tblog.com/2019/10/03/vue-input/

やったこと
(1):v-bind.syncを使いbasic_infoを子に渡す
親↓

vue

1 <basicInfoForm 2 v-bind.sync="basic_info" 3 @submit="editBasictest" 4 @close="closeForm" 5 ></basicInfoForm>

(2)子のpropsでbasic_infoを展開。

props: { start_date: { type: String, default: "", }, end_date: { type: String, default: "", }, destination: { type: String, default: "", }, goal: { type: String, default: "", }, }

子↓ フォームに入力があった場合親に連携する(updateの部分)

vue

1<v-text-field 2 label="start from*" 3 :rules="[rules.required]" 4 :value="start_date" 5 @update="$emit('update:start_date', $event.target.start_date)">

(3)submit実行により子のデータを親側で取得する
子↓

<v-btn type="submit" color="blue darken-1" text class="mr-3" @click="$emit('submit')" >start Planning</v-btn>

親↓

vue

1 <basicInfoForm 2  v-bind.sync="basic_info" 3 @submit="editBasictest" 4 @close="closeForm" 5 ></basicInfoForm> 6 7  editBasictest() { 8 const self = this; 9 self.basic_info_dialog = !self.basic_info_dialog; 10 console.log(this.basic_info.start_date); 11 console.log(this.basic_info.end_date); 12 console.log(this.basic_info.destination); 13 console.log(this.basic_info.goal); 14 }, 15

このconsole.logの結果が、子のフォームで変更した値ではなく、
フォームを開いた時点での親から子に渡された初期値が帰ってきてしまいます。

試したこと

原因を確かめるため、以下のように変更してみたのですが解決しませんでした

(1) 子コンポーネントのtext-formの設定を変更してみた

@update="$emit('update:end_date', $event.target.end_date)"

@input="$emit('update:end_date', $event.target.end_date)"

→エラーになった

[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'start_date' of undefined"

(2) 子コンポーネントでsubmitした値が取れるかconsole.logで出力

<v-btn type="submit" color="blue darken-1" text class="mr-3" @click="sendBasicData" >start Planning</v-btn >  sendBasicData() { console.log(this.start_date); console.log(this.end_date); console.log(this.destination); console.log(this.goal); },

→親に渡そうとした時と同様、初期値だった(そもそもsubmitで値が取れていない)

環境:
"vue": "^2.6.11",
"vuetify": "^2.2.11"

以下、コード全体

親コンポーネント

<template> <div> <v-card class="mx-5 my-5" max-width="800"> <v-img class="white--text align-end" height="200px" :src="image_src"> <v-card-title>{{ basic_info.destination }}</v-card-title> </v-img> <v-card-subtitle class="title" >{{ basic_info.start_date }} 〜 {{ basic_info.end_date }}</v-card-subtitle > <v-card-text class="text--primary"> {{ basic_info.goal }} </v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-dialog v-model="basic_info_dialog" persistent max-width="600px"> <template v-slot:activator="{ on }"> <v-btn v-on="on" text>Edit</v-btn> </template> <basicInfoForm v-bind.sync="basic_info" @submit="editBasictest" @close="closeForm" ></basicInfoForm> </v-dialog> <v-btn text> <router-link v-bind:to="{ name: 'plan', params: { travel_id: travel_id } }" >Detail</router-link > </v-btn> </v-card-actions> </v-card> </div> </template> <script> import basicInfoForm from "./basicInfoForm"; export default { props: { travel_id: {}, }, components: { basicInfoForm, }, data() { return { image_src: require("../assets/stockholm.jpg"), basic_info: { destination: null, start_date: null, end_date: null, goal: null, }, basic_info_dialog: null, hasNoTravels: null, }; }, created() { this.getUserBasicData(); }, methods: { getUserBasicData: async function() { console.log(this.travel_id); const db = this.$firebase.firestore(); const info_ref = db.collection("basic_info").doc(this.travel_id); info_ref.onSnapshot((doc) => { this.basic_info = doc.data(); }); }, closeForm() { this.basic_info_dialog = !this.basic_info_dialog; }, editBasictest() { const self = this; self.basic_info_dialog = !self.basic_info_dialog; console.log(this.basic_info.start_date); console.log(this.basic_info.end_date); console.log(this.basic_info.destination); console.log(this.basic_info.goal); }, editBasicData() { const self = this; self.basic_info_dialog = !self.basic_info_dialog; // basic_infoのデータを編集 const db = this.$firebase.firestore(); const batch = db.batch(); const info_ref = db.collection("basic_info").doc(this.travel_id); batch.update(info_ref, { start_date: this.basic_info.start_date, end_date: this.basic_info.end_date, destination: this.basic_info.destination, goal: this.basic_info.goal, }); batch .commit() .then(function() { console.log(); console.log(); console.log("batch end"); return; }) .catch(function(error) { console.log(error); self.hasError = true; self.errorMessage = "batch failed"; return; }); }, }, }; </script> コード

子コンポーネント(basicInfoForm.vue)

<template> <v-card> <v-card-title> <span class="headline">add new Travel</span> </v-card-title> <v-form ref="basic_info" @submit.prevent> <v-card-text> <v-container> <v-row> <!-- https://qiita.com/fukuman/items/b0bc84081ad0d2bc522aあとでこれで直す --> <v-col cols="12" sm="6" md="4"> <v-text-field label="start from*" :rules="[rules.required]" :value="start_date" @update="$emit('update:start_date', $event.target.start_date)" > <template v-slot:append-outer> <date-picker v-model="start_date" /> </template> </v-text-field> </v-col> <v-col cols="12" sm="6" md="4"> <v-text-field label="come back on*" :rules="[rules.required]" :value="end_date" @update="$emit('update:end_date', $event.target.end_date)" > <template v-slot:append-outer> <date-picker v-model="end_date" /> </template> </v-text-field> </v-col> <v-col cols="12"> <v-text-field label="destination*" :rules="[rules.required]" :value="destination" @update="$emit('update:destination', $event.target.destination)" ></v-text-field> </v-col> <v-col cols="12"> <v-text-field label="goal" :value="goal" @update="$emit('update:goal', $event.target.goal)" ></v-text-field> </v-col> </v-row> </v-container> <small>*indicates required field</small> </v-card-text> <v-row> <v-spacer></v-spacer> <v-btn color="blue darken-1" text @click="close">close</v-btn> <v-btn type="submit" color="blue darken-1" text class="mr-3" @click="sendBasicData" >start Planning</v-btn > </v-row> </v-form> </v-card> </template> <script> import DatePicker from "./DatePicker"; export default { name: "App", components: { DatePicker, }, props: { start_date: { type: String, default: "", }, end_date: { type: String, default: "", }, destination: { type: String, default: "", }, goal: { type: String, default: "", }, }, data() { return { rules: { required: (value) => !!value || "必ず入力してください", }, }; }, methods: { close() { this.$emit("close"); }, sendBasicData() { console.log(this.start_date); console.log(this.end_date); console.log(this.destination); console.log(this.goal); }, }, }; </script>

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

とりあえず何となく動いてるように見えるだけのコードにしてみました。

  • firebase絡みを削除
  • datepicker絡みを削除してv-date-pickerに置き換え
  • .vueのままじゃ共通コードにならなそうだったので動くコードにしつつHTML1本に書き直し
  • 要件に関係ない部分を削除

をベースにし、修正した感じです。

問題点はいくつかあります。

  1. vuetifyのコンポーネントに適したイベントやフィールドになっていない
  2. datepickerがv-text-fieldを書き換えるが、v-text-fieldのイベントでそれを拾えない

2の問題に対応するためには、datepickerとv-text-fieldと親コンポーネントの3つが競合するので、リアルタイムに更新を拾うとややこしすぎるため、submit時にemitする方向に変更。datepickerだけそうした場合、親更新時に子のプロパティ全てが親のプロパティで上書きされるため、全プロパティのemitをsubmit時に移さざるを得なかった。

以下が対応したコードです。正直危なっかしいです。状態管理の方が安全な気がします。

HTML

1<head> 2 <meta charset="utf-8"> 3 <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"> 4 <link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet"> 5 <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet"> 6 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"> 7 <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script> 8 <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script> 9 <script> 10 Vue.use(Vuetify); 11 window.addEventListener('DOMContentLoaded', () => { 12 const child = { 13 template: ` 14 <v-card> 15 <v-card-text> 16 <v-container> 17 <v-row> 18 <v-col cols="12" sm="6" md="4"> 19 <v-text-field label="start from*" :rules="[rules.required]" :value="start_date"> 20 <template v-slot:append-outer> 21 <v-date-picker v-model="start_date" /> 22 </template> 23 </v-text-field> 24 </v-col> 25 <v-col cols="12" sm="6" md="4"> 26 <v-text-field label="come back on*" :rules="[rules.required]" :value="end_date"> 27 <template v-slot:append-outer> 28 <v-date-picker v-model="end_date" /> 29 </template> 30 </v-text-field> 31 </v-col> 32 <v-col cols="12"> 33 <v-text-field label="destination*" :rules="[rules.required]" v-model="destination"></v-text-field> 34 </v-col> 35 <v-col cols="12"> 36 <v-text-field label="goal" v-model="goal"></v-text-field> 37 </v-col> 38 </v-row> 39 </v-container> 40 <small>*indicates required field</small> 41 </v-card-text> 42 <v-row> 43 <v-btn type="submit" color="blue darken-1" text class="mr-3" @click="sendBasicData">start Planning</v-btn> 44 </v-row> 45 </v-card> 46 `, 47 name: "App", 48 props: { 49 start_date: { 50 type: String, 51 default: "", 52 }, 53 end_date: { 54 type: String, 55 default: "", 56 }, 57 destination: { 58 type: String, 59 default: "", 60 }, 61 goal: { 62 type: String, 63 default: "", 64 }, 65 }, 66 data() { 67 return { 68 rules: { 69 required: (value) => !!value || "必ず入力してください", 70 }, 71 }; 72 }, 73 methods: { 74 sendBasicData() { 75 console.log(this.start_date); 76 console.log(this.end_date); 77 console.log(this.destination); 78 console.log(this.goal); 79 const start_date = this.start_date; 80 const end_date = this.end_date; 81 const destination = this.destination; 82 const goal = this.goal; 83 this.$emit('update:start_date', start_date); 84 this.$emit('update:end_date', end_date); 85 this.$emit('update:destination', destination); 86 this.$emit('update:goal', goal); 87 this.$emit("submit"); 88 }, 89 }, 90 }; 91 const parent = { 92 template: ` 93 <v-card class="mx-5 my-5" max-width="800"> 94 <v-card-title>{{ basic_info.destination }}</v-card-title> 95 <v-card-subtitle class="title">{{ basic_info.start_date }} 〜 {{ basic_info.end_date }}</v-card-subtitle> 96 <v-card-text class="text--primary">{{ basic_info.goal }}</v-card-text> 97 <v-card-actions> 98 <v-dialog v-model="basic_info_dialog" persistent max-width="600px"> 99 <template v-slot:activator="{ on }"> 100 <v-btn v-on="on" text>Edit</v-btn> 101 </template> 102 <basicInfoForm v-bind.sync="basic_info" @submit="editBasictest" @close="closeForm"></basicInfoForm> 103 </v-dialog> 104 </v-card-actions> 105 </v-card> 106 `, 107 components: { 108 basicInfoForm: child, 109 }, 110 data() { 111 return { 112 basic_info: { 113 destination: null, 114 start_date: null, 115 end_date: null, 116 goal: null, 117 }, 118 basic_info_dialog: null, 119 }; 120 }, 121 methods: { 122 closeForm() { 123 this.basic_info_dialog = !this.basic_info_dialog; 124 }, 125 editBasictest() { 126 const self = this; 127 self.basic_info_dialog = !self.basic_info_dialog; 128 console.log(this.basic_info.start_date); 129 console.log(this.basic_info.end_date); 130 console.log(this.basic_info.destination); 131 console.log(this.basic_info.goal); 132 }, 133 }, 134 }; 135 new Vue({ 136 el: '#app', 137 vuetify: new Vuetify(), 138 components: { 139 app: parent, 140 }, 141 }); 142 }); 143 </script> 144</head> 145<body> 146 <div id="app"> 147 <v-app> 148 <v-main> 149 <app></app> 150 </v-main> 151 </v-app> 152 </div> 153</body>

投稿2020/08/27 16:48

編集2020/08/27 17:26
dameo

総合スコア943

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mellan22

2020/08/30 08:22

丁寧なご回答ありがとうございます!! いただいたコードを参考にしつつ、vuexを使って書き直してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問