0
votes

I tried to make a chat app using Vue CLI 3 and I have finished making a real-time chat room. Then, I tried to give a citing function to it which users can cite the message before and reply to it. So, I manage to pass the cited message to the child component by props. The cited message was NULL by default. After the user clicked some buttons, I expected the value of the "cited message" would change and the new value would be passed to the child through props (automatically updated). But, in fact, it didn't.

When I was browsing the internet, I did find several questions about updating the child component when props values change. So, I tried watch:, created(), update(), but none of them worked. I once tried to directly add an element <p> in the child component and put {{cited_message}} in it to see what was inside the variable. Then, the Vue app crashed with a white blank page left (but the console didn't show any error).

For convenience, I think the problem is around:

<CreateMessage:name="name":cited_message="this.cited_message"@interface="handleFcAfterDateBack"/>

OR

props: ["name","cited_message"], 
watch: { cited_message: function (newValue){ this.c_message = newValue; } },

You can ctrl+F search for the above codes to save your time.

Parent component:

<template>
  <div class="container chat">
    <h2 class="text-primary text-center">Real-time chat</h2>
    <h5 class="text-secondary text-center">{{ name }}</h5>
    <div class="card" style="min-height: 0.8vh">
      <div class="card-body">
        <p class="text-secondary nomessages" v-if="messages.length == 0">
          [no messages yet!]
        </p>
        <div class="messages" v-chat-scroll="{ always: false, smooth: false }">
          <div v-for="message in messages" :key="message.id">
            <div v-if="equal_name(message)">
              <div class="d-flex flex-row">
                <div class="text-info">
                  [ {{ message.name }} ] : {{ message.message }}
                </div>

                <div class="btn-group dropright">
                  <a
                    class="btn btn-secondary btn-sm dropdown-toggle"
                    href="#"
                    role="button"
                    id="dropdownMenuLink"
                    data-toggle="dropdown"
                    aria-haspopup="true"
                    aria-expanded="false"
                  >
                  </a>

                  <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                    <button
                      @click="get_cited_message(message)"
                      :key="message.id"
                      class="dropdown-item"
                      href="#"
                    >
                      Cite
                    </button>
                  </div>
                </div>

                <div class="text-secondary time">
                  <sub>{{ message.timestamp }}</sub>
                </div>
              </div>
              <!--below is for cited message-->
              <div v-if="message.cited_message" class="d-flex flex-row">
                Cited  : {{ message.cited_message }}
              </div>
            </div>

            <div v-else>
              <div class="d-flex flex-row-reverse">
                <div class="text-info">
                  [ {{ message.name }} ] : {{ message.message }}
                </div>
                <div class="text-secondary time">
                  <sub>{{ message.timestamp }}</sub>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="card-action">
        <CreateMessage
          :name="name"
          :cited_message="this.cited_message"
          @interface="handleFcAfterDateBack"
        />
      </div>
    </div>
  </div>
</template>

<script>
import CreateMessage from "@/components/CreateMessage";
import fb from "@/firebase/init.js";
import moment from "moment";

export default {
  name: "Chat",
  props: {
    name: String,
  },
  components: {
    CreateMessage,
  },
  methods: {
    equal_name(message) {
      if (message.name == this.name) {
        return true;
      } else {
        return false;
      }
    },
    get_cited_message(message) {
      this.cited_message = message.message;
      console.log(this.cited_message);
    },
    handleFcAfterDateBack(event) {
      console.log("data after child handle: ", event);
    },
  },
  data() {
    return {
      messages: [],
      cited_message: null,
    };
  },
  created() {
    let ref = fb.collection("messages").orderBy("timestamp");
    ref.onSnapshot((snapshot) => {
      snapshot.docChanges().forEach((change) => {
        change.type = "added";
        if (change.type == "added") {
          let doc = change.doc;
          this.messages.push({
            id: doc.id,
            name: doc.data().name,
            message: doc.data().message,
            timestamp: moment(doc.data().timestamp).format(
              "MMMM Do YYYY, h:mm:ss a"
            ),
            cited_message: doc.data().cited_message,
          });
        }
      });
    });
  },
};
</script>

<style>
.chat h2 {
  font-size: 2.6em;
  margin-bottom: 0px;
}
.chat h5 {
  margin-top: 0px;
  margin-bottom: 40px;
}
.chat span {
  font-size: 1.2em;
}
.chat .time {
  display: block;
  font-size: 0.7em;
}
.messages {
  max-height: 300px;
  overflow: auto;
  text-align: unset;
}
.d-flex div {
  margin-left: 10px;
}
</style>

Child component:

<template>
  <div class="container" style="margin-bottom: 30px">
    <form @submit.prevent="createMessage()">
      <div class="form-group">
        <input
          type="text"
          name="message"
          class="form-control"
          placeholder="Enter your message"
          v-model="newMessage"
        />
        <p v-if="c_message" class="bg-secondary text-light">Cited:  {{c_message}}</p>
        <p class="text-danger" v-if="errorText">{{ errorText }}</p>
      </div>
      <button class="btn btn-primary" type="submit" name="action">
        Submit
      </button>
    </form>
  </div>
</template>
<script>
import fb from "@/firebase/init.js";
import moment from "moment";

export default {
  name: "CreateMessage",
  props: ["name","cited_message"],
  watch: {
    cited_message: function (newValue){
      this.c_message = newValue;
    }
  },
  data() {
    return {
      newMessage: "",
      errorText: null,
      c_message: null
    };
  },
  methods: {
    createMessage() {
      if (this.newMessage) {
        fb.collection("messages")
          .add({
            message: this.newMessage,
            name: this.name,
            timestamp: moment().format(),
            cited_message: this.c_message
          })
          .then(function(docRef) {
            console.log("Document written with ID: ", docRef.id);
          })
          .catch((err) => {
            console.log(err);
          });
        this.newMessage = null;
        this.errorText = null;
      } else {
        this.errorText = "Please enter a message!";
      }
    },
  },
  beforeMount(){
    this.c_message = this.cited_message;
  }
  
  
};
</script>

Side-note: In the parent component, I only made the dropdown menu for the messages on the left-hand side. If this thread solved, I would finish the right-hand side.

1

1 Answers

0
votes

It is solved. I think the problem is that the child component didn't re-render when the variable in parent component updated. Only the parent component was re-rendered. So, the props' values in the child remained the initial values. In order to solve this, binding the element with v-bind:key can let the Vue app track the changes of the variable (like some kind of a reminder that reminds the app to follow the changes made on the key). When the variable(key) changes, the app will be noticed and the new value will be passed to the child.

E.g. Original

<CreateMessage
          :name="name"
          :cited_message="this.cited_message"
          @interface="handleFcAfterDateBack"
        />

Solved

<CreateMessage
          :name="name"
          :cited_message="this.cited_message"
          @interface="handleFcAfterDateBack"
          :key="this.cited_message"
        />

Even though the problem is solved, I don't know whether I understand the problem clearly. If I made any mistakes, please comment and let me know.