iwantit
@iwantit

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Vue.js + Laravel8でv-ifが上手く動作しない

【開発環境】
Windows 10 HOME
PHP 7.4.7
laravel/framework: ^8.2,
vue": "^2.5.17",

解決したいこと

Vue.js + Laravelの勉強で
ローカルで開発しています。

ログイン機能をつけているのですが、
ログイン後のタスク一覧のヘッダー部分の
v-if が上手く動作しないようです。

ログイン後にもう一度
画面を更新するとログイン後に表示したいボタンの
一覧が表示されます。

【ログイン前】
・ログインボタン

【ログイン後】
・一覧ページボタン
・新規登録ページボタン
・ログアウトボタン

ヘッダー部分

resources\js\components\HeaderComponent.vue
<template>
    <div class="container-fluid bg-dark mb-3">
        <div class="container">
            <nav class="navbar navbar-dark">
                <span class="navbar-brand mb-0 h1">Vue Laravel SPA</span>

                <!-- Authenticated -->
                <div v-if="user">
                    <router-link v-bind:to="{name:'task.list'}">
                        <button class="btn btn-success">List</button>
                    </router-link>
                    <router-link v-bind:to="{name:'task.create'}">
                        <button class="btn btn-success">ADD</button>
                    </router-link>

                    <button type="button" class="btn btn-danger" @click="logout">Logout</button>

                </div>

                <!-- Gusst -->
                <div v-else>
                    <router-link v-bind:to="{name:'login'}">
                        <button class="btn btn-success">login</button>
                    </router-link>
                </div>

            </nav>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            user: ""
        };
    },
    mounted() {
        axios.get("/api/user").then(response => {
            this.user = response.data;
        });
    },
    methods: {
        logout() {
            axios
                .post("api/logout")
                .then(response => {
                    console.log(response);
                    localStorage.removeItem("auth");
                    this.$router.push("/login");
                })
                .catch(error => {
                    console.log(error);
                });
        }
    },

};
</script>

何か心当たりがある方、
解決方法を教えて下さい。

2021年6月26日(土) 14:00 追記

ルートファイルでHeaderComponentを
読み込むのを止めて

resources\views\index.blade.php
    <div id="app">
        <HeaderComponent></HeaderComponent>
        <router-view />
    </div>

resources\views\index.blade.php
    <div id="app">
        <router-view />
    </div>

TaskListComponent画面でHeaderComponent部分を
下記のように読み込むことで想定通りの動作が確認出来ました。

resources\js\components\TaskListComponent.vue
<template>
    <div>
        <HeaderComponent></HeaderComponent> // 追記

        <div class="container">
            <table class="table table-hover">
                <thead class="thead-light">
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">Title</th>
                    <th scope="col">Content</th>
                    <th scope="col">Person In Charge</th>
                    <th scope="col">Show</th>
                    <th scope="col">Edit</th>
                    <th scope="col">Delete</th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(task, index) in tasks" :key="index">
                    <th scope="row">{{ task.id }}</th>
                    <td>{{ task.title }}</td>
                    <td>{{ task.content }}</td>
                    <td>{{ task.person_in_charge }}</td>
                    <td>
                        <router-link v-bind:to="{name: 'task.show', params: {taskId: task.id }}">
                            <button class="btn btn-primary">Show</button>
                        </router-link>
                    </td>
                    <td>
                        <router-link v-bind:to="{name: 'task.edit', params: {taskId: task.id }}">
                            <button class="btn btn-success">Edit</button>
                        </router-link>
                    </td>
                    <td>
                        <button class="btn btn-danger" v-on:click="deleteTask(task.id)">Delete</button>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script>
import HeaderComponent from "./HeaderComponent.vue" // 追記


    export default {

        components: { 
            HeaderComponent // 追記
        },

        data() {
            return {
                tasks: []
            };
        },

        methods: {
            getTasks() {
                axios.get('/api/tasks')
                    .then((res) => {
                        this.tasks = res.data;
                    });
            },
            deleteTask(id) {
                axios.delete('/api/tasks/' + id)
                    .then((res) => {
                        this.getTasks();
                    });
            }
        },
        mounted() {
            this.getTasks();
        }
    }
</script>

0

3Answer

Chrome/FireFoxなどでVue.js toolsと言うエクステンションを使えば、dataが見られます。それでthis.userは空なのか確認ができます。
console.log()でコンソールにデータを映す事もできます。

1Like

Comments

  1. @iwantit

    Questioner

    @skys215様

    こちらにもコメントありがとうございます。
    Vue.js toolsで中身をみた所、
    ログイン後に「user」の中身が空になっていました。
    この辺に原因がありそうなのでもう少し調査してみます。
  • ログイン処理は実行されている
  • リロードするとログイン後の画面が表示される

なら、原因はログイン処理後の対応かもしれません。

  • apiから返される情報が正しいか?
  • vue側で受け取る処理が正しく設定されているか?

上2つの確認をオススメします。

1Like

Comments

  1. @iwantit

    Questioner

    @harmless_3d6 様

    具体的なアドバイスありがとうございます。
    Vue.js toolsで中身をみた所、
    ログイン後に「user」の中身が空になっており、
    コンソールで「user」に有るべきデータを入れてみた所
    ログイン後のヘッダー部分に切り替わりました。

    なのでご指摘頂いた2点が
    原因である可能性が濃厚のようなのでもう少し調べてみます。

コンポーネントの構成やログイン処理が不明なので断言はできないですが、userHeaderComponent.vue のローカルデータであり、なおかつ書き換えられるタイミングが同コンポーネントの mounted ライフサイクルのみなのが原因かと思います。

このライフサイクルはコンポーネントがDOM要素として既にページ内にマウントされていれば破棄されない限り再び呼び出されることはないので、ログインの前後について例えば「vue-router での遷移である」「ログイン前からヘッダーが表示されている」という状況なのであれば、ヘッダーはマウントされっぱなしなので結果として user に変更が起こらないことになります。

ログイン後に「user」の中身が空になっており

よってログイン後に空になっているのではなく、ログイン前の空の状態から何も変更されていない、というのが正しい認識かと思われます。

一度ログインを行った後にブラウザのリロードを行えばすべてのコンポーネントが最初から生成されることになるため、mounted ライフサイクルで想定通りログイン後の response.data が得られるのではないでしょうか。

1Like

Comments

  1. @iwantit

    Questioner

    @prismrism様

    コメントありがとうございます。

    私も似たようなことを考えて
    HeaderComponent.vueのmounted の中に
    alertを仕込んてみたらログイン時は呼ばれず、
    リロード時にやっと呼ばれました。

    ですのでコメント頂いた通り

    「ログイン前の空の状態から何も変更されていない」
    のが分かりました。

    HeaderComponent.vueを呼んでいる
    一覧画面のTaskListComponent.vueの
    mounted にはどう書けば動くか教えて頂けませんか?

  2. 質問内容に関してですが、TaskListComponent.vue とログイン処理の関係が不明なのでこれの mounted を書き換えるだけで対処できるかはアドバイスできないです。
    TaskListComponent.vue の詳細については @silversink8888 さんの他の質問を覗いてようやく「これかな?」と察することができますが、本質問に記載のない情報は明示して頂く方が認識に齟齬がないと思います。


    さて、今回の問題を解決するには「ログイン処理よりも後に HeaderComponent.vue の user を変更する」という要件を満たす必要があるわけですが、

    [1] mounted でしか user が変更されていない
    [2] ログイン処理が HeaderComponent.vue の外部にある
    [3] user はこのコンポーネントのローカルプロパティであり、外部から操作できない

    というのが障壁になっているので、これらを総合すると下記のいずれかで対処していくことが多いのかなと思います。

    [A] HeaderComponent.vue のイベント登録・送信を利用する。 mounted でやってるのと同じことを外部から任意のタイミングで実行できるようにする(vm.$on, vm.$emit)。
    [B] user を親コンポーネントから HeaderComponent.vue に渡し、親コンポーネント側から操作可能にする(v-bind, props)。
    [C] user を Vuex のステートとして定義しなおす。HeaderComponent.vue ではこれを参照する computed プロパティを v-if と連動させ、ログイン処理では $store.dispatch で user を間接的に変更する。

    axios.get() の実行は必要最低限の回数が良いですし、ログイン後のユーザー情報はあちこちのコンポーネントで使用されるものだと思うので、個人的には [C] で対応するのが王道かな~と感じます。
  3. @iwantit

    Questioner

    @prismrism 様

    詳細なコメントありがとうございます。
    かなり勉強になります。
    [C]の方法でやってみようと思います。
  4. @iwantit

    Questioner

    @prismrism様

    教えて頂いたC案で暫くやってみたのですが、
    中々想定通りの動きが出来なかったので
    C案からヒントを受けて下記のようにしました。

    TaskListComponent画面でHeaderComponent部分を
    読み込むことで想定通りの動作が確認出来ました。
    (HeaderComponentを読み込むともう一度作られるみたいです)

    ソースも追加しましたのでお時間があれば見て頂ければと思います。
    コメントを頂きありがとうございました。

Your answer might help someone💌