最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Vuex load existing form data from state to edit - Stack Overflow

programmeradmin1浏览0评论

I followed through this tutorial to try and learn Vue, I've finished and it works, but I'm trying to make a change that I'm struggling with.

So there's a "settings" page which has the user profile (they can edit their name etc). When that "settings" / "profile" page loads I want the form to load their existing data so they can just modify it and press save.

It currently loads as a placeholder with :placeholder="userProfile.name" - I want it to just fill the form with the actual value instead of having it as a placeholder.

It feels like it should be ridiculously simple to do this but can't get it working elegantly.

Settings.vue

<template>
  <section id="settings">
    <div class="col1">
      <h3>Settings</h3>
      <p>Update your profile</p>

      <transition name="fade">
        <p v-if="showSuccess" class="success">profile updated</p>
      </transition>

      <form @submit.prevent>
        <label for="name">Name</label>
        <input v-model.trim="name" type="text" id="name" />

        <label for="title">Job Title</label>
        <input v-model.trim="title" type="text" id="title" />

        <button @click="updateProfile()" class="button">Update Profile</button>
      </form>
    </div>
  </section>
</template>

<script>
import { mapState } from "vuex";

export default {
  data() {
    return {
      name: "",
      title: "",
      showSuccess: false,
    };
  },
  puted: {
    ...mapState(["userProfile"]),
  },
  methods: {
    updateProfile() {
      this.$store.dispatch("updateProfile", {
        name: this.name !== "" ? this.name : this.userProfile.name,
        title: this.title !== "" ? this.title : this.userProfile.title,
      });

      this.name = "";
      this.title = "";

      this.showSuccess = true;

      setTimeout(() => {
        this.showSuccess = false;
      }, 2000);
    },
  },
};
</script>

<style lang="scss" scoped>
</style>

I tried changing the data section to this, which works when I leave the page and go back to it, but if I refresh the page (F5) the fields are blank until I leave the page and e back again.

data() {
    return {
      name: this.$store.state.userProfile.name,
      title: this.$store.state.userProfile.title,
      showSuccess: false,
    };
  },

And here's my store if you need to see that:

store/index.js

import Vue from "vue";
import Vuex from "vuex";
import * as fb from "../firebase";
import router from "../router/index";

Vue.use(Vuex);

// realtime firebase connection
fb.postsCollection.orderBy("createdOn", "desc").onSnapshot((snapshot) => {
  let postsArray = [];

  snapshot.forEach((doc) => {
    let post = doc.data();
    post.id = doc.id;

    postsArray.push(post);
  });

  storemit("setPosts", postsArray);
});

const store = new Vuex.Store({
  state: {
    userProfile: {},
    posts: [],
  },
  mutations: {
    setUserProfile(state, val) {
      state.userProfile = val;
    },
    setPosts(state, val) {
      state.posts = val;
    },
  },
  actions: {
    async signup({ dispatch }, form) {
      // sign user up
      const { user } = await fb.auth.createUserWithEmailAndPassword(
        form.email,
        form.password
      );

      // create user profile object in userCollections
      await fb.usersCollection.doc(user.uid).set({
        name: form.name,
        title: form.title,
      });

      // fetch user profile and set in state
      dispatch("fetchUserProfile", user);
    },
    async login({ dispatch }, form) {
      // sign user in
      const { user } = await fb.auth.signInWithEmailAndPassword(
        form.email,
        form.password
      );

      // fetch user profile and set in state
      dispatch("fetchUserProfile", user);
    },
    async logout({ mit }) {
      await fb.auth.signOut();

      // clear userProfile and redirect to /login
      mit("setUserProfile", {});
      router.push("/login");
    },
    async fetchUserProfile({ mit }, user) {
      // fetch user profile
      const userProfile = await fb.usersCollection.doc(user.uid).get();

      // set user profile in state
      mit("setUserProfile", userProfile.data());

      // change route to dashboard
      if (router.currentRoute.path === "/login") {
        router.push("/");
      }
    },
    async createPost({ state }, post) {
      await fb.postsCollection.add({
        createdOn: new Date(),
        content: post.content,
        userId: fb.auth.currentUser.uid,
        userName: state.userProfile.name,
        ments: 0,
        likes: 0,
      });
    },
    async likePost(context, { id, likesCount }) {
      const userId = fb.auth.currentUser.uid;
      const docId = `${userId}_${id}`;

      // check if user has liked post
      const doc = await fb.likesCollection.doc(docId).get();
      if (doc.exists) {
        return;
      }

      // create post
      await fb.likesCollection.doc(docId).set({
        postId: id,
        userId: userId,
      });

      // update post likes count
      fb.postsCollection.doc(id).update({
        likes: likesCount + 1,
      });
    },
    async updateProfile({ dispatch }, user) {
      const userId = fb.auth.currentUser.uid;
      // update user object
      /*const userRef = */await fb.usersCollection.doc(userId).update({
        name: user.name,
        title: user.title,
      });

      dispatch("fetchUserProfile", { uid: userId });

      // update all posts by user
      const postDocs = await fb.postsCollection
        .where("userId", "==", userId)
        .get();
      postDocs.forEach((doc) => {
        fb.postsCollection.doc(doc.id).update({
          userName: user.name,
        });
      });

      // update all ments by user
      const mentDocs = await fbmentsCollection
        .where("userId", "==", userId)
        .get();
      mentDocs.forEach((doc) => {
        fbmentsCollection.doc(doc.id).update({
          userName: user.name,
        });
      });
    },
  },
  modules: {},
});

export default store;

EDIT

I should have mentioned that this data is being loaded into the state from a Firebase Firestore. It looks like it's just a timing thing, the data isn't quite loaded by the time it sets the data() on the ponent - I added some console logs.

Fetching user profile.. Settings.vue?e12e:29
Setting Data... index.js?4360:75
Performing setUserProfile mit.. index.js?4360:29
Setting user profile in state, last step..

Again just don't know enough about Vue yet to know how to best change that order..

I followed through this tutorial to try and learn Vue, I've finished and it works, but I'm trying to make a change that I'm struggling with.

https://savvyapps./blog/definitive-guide-building-web-app-vuejs-firebase

So there's a "settings" page which has the user profile (they can edit their name etc). When that "settings" / "profile" page loads I want the form to load their existing data so they can just modify it and press save.

It currently loads as a placeholder with :placeholder="userProfile.name" - I want it to just fill the form with the actual value instead of having it as a placeholder.

It feels like it should be ridiculously simple to do this but can't get it working elegantly.

Settings.vue

<template>
  <section id="settings">
    <div class="col1">
      <h3>Settings</h3>
      <p>Update your profile</p>

      <transition name="fade">
        <p v-if="showSuccess" class="success">profile updated</p>
      </transition>

      <form @submit.prevent>
        <label for="name">Name</label>
        <input v-model.trim="name" type="text" id="name" />

        <label for="title">Job Title</label>
        <input v-model.trim="title" type="text" id="title" />

        <button @click="updateProfile()" class="button">Update Profile</button>
      </form>
    </div>
  </section>
</template>

<script>
import { mapState } from "vuex";

export default {
  data() {
    return {
      name: "",
      title: "",
      showSuccess: false,
    };
  },
  puted: {
    ...mapState(["userProfile"]),
  },
  methods: {
    updateProfile() {
      this.$store.dispatch("updateProfile", {
        name: this.name !== "" ? this.name : this.userProfile.name,
        title: this.title !== "" ? this.title : this.userProfile.title,
      });

      this.name = "";
      this.title = "";

      this.showSuccess = true;

      setTimeout(() => {
        this.showSuccess = false;
      }, 2000);
    },
  },
};
</script>

<style lang="scss" scoped>
</style>

I tried changing the data section to this, which works when I leave the page and go back to it, but if I refresh the page (F5) the fields are blank until I leave the page and e back again.

data() {
    return {
      name: this.$store.state.userProfile.name,
      title: this.$store.state.userProfile.title,
      showSuccess: false,
    };
  },

And here's my store if you need to see that:

store/index.js

import Vue from "vue";
import Vuex from "vuex";
import * as fb from "../firebase";
import router from "../router/index";

Vue.use(Vuex);

// realtime firebase connection
fb.postsCollection.orderBy("createdOn", "desc").onSnapshot((snapshot) => {
  let postsArray = [];

  snapshot.forEach((doc) => {
    let post = doc.data();
    post.id = doc.id;

    postsArray.push(post);
  });

  store.mit("setPosts", postsArray);
});

const store = new Vuex.Store({
  state: {
    userProfile: {},
    posts: [],
  },
  mutations: {
    setUserProfile(state, val) {
      state.userProfile = val;
    },
    setPosts(state, val) {
      state.posts = val;
    },
  },
  actions: {
    async signup({ dispatch }, form) {
      // sign user up
      const { user } = await fb.auth.createUserWithEmailAndPassword(
        form.email,
        form.password
      );

      // create user profile object in userCollections
      await fb.usersCollection.doc(user.uid).set({
        name: form.name,
        title: form.title,
      });

      // fetch user profile and set in state
      dispatch("fetchUserProfile", user);
    },
    async login({ dispatch }, form) {
      // sign user in
      const { user } = await fb.auth.signInWithEmailAndPassword(
        form.email,
        form.password
      );

      // fetch user profile and set in state
      dispatch("fetchUserProfile", user);
    },
    async logout({ mit }) {
      await fb.auth.signOut();

      // clear userProfile and redirect to /login
      mit("setUserProfile", {});
      router.push("/login");
    },
    async fetchUserProfile({ mit }, user) {
      // fetch user profile
      const userProfile = await fb.usersCollection.doc(user.uid).get();

      // set user profile in state
      mit("setUserProfile", userProfile.data());

      // change route to dashboard
      if (router.currentRoute.path === "/login") {
        router.push("/");
      }
    },
    async createPost({ state }, post) {
      await fb.postsCollection.add({
        createdOn: new Date(),
        content: post.content,
        userId: fb.auth.currentUser.uid,
        userName: state.userProfile.name,
        ments: 0,
        likes: 0,
      });
    },
    async likePost(context, { id, likesCount }) {
      const userId = fb.auth.currentUser.uid;
      const docId = `${userId}_${id}`;

      // check if user has liked post
      const doc = await fb.likesCollection.doc(docId).get();
      if (doc.exists) {
        return;
      }

      // create post
      await fb.likesCollection.doc(docId).set({
        postId: id,
        userId: userId,
      });

      // update post likes count
      fb.postsCollection.doc(id).update({
        likes: likesCount + 1,
      });
    },
    async updateProfile({ dispatch }, user) {
      const userId = fb.auth.currentUser.uid;
      // update user object
      /*const userRef = */await fb.usersCollection.doc(userId).update({
        name: user.name,
        title: user.title,
      });

      dispatch("fetchUserProfile", { uid: userId });

      // update all posts by user
      const postDocs = await fb.postsCollection
        .where("userId", "==", userId)
        .get();
      postDocs.forEach((doc) => {
        fb.postsCollection.doc(doc.id).update({
          userName: user.name,
        });
      });

      // update all ments by user
      const mentDocs = await fb.mentsCollection
        .where("userId", "==", userId)
        .get();
      mentDocs.forEach((doc) => {
        fb.mentsCollection.doc(doc.id).update({
          userName: user.name,
        });
      });
    },
  },
  modules: {},
});

export default store;

EDIT

I should have mentioned that this data is being loaded into the state from a Firebase Firestore. It looks like it's just a timing thing, the data isn't quite loaded by the time it sets the data() on the ponent - I added some console logs.

Fetching user profile.. Settings.vue?e12e:29
Setting Data... index.js?4360:75
Performing setUserProfile mit.. index.js?4360:29
Setting user profile in state, last step..

Again just don't know enough about Vue yet to know how to best change that order..

Share Improve this question edited Sep 24, 2020 at 0:25 BT643 asked Sep 22, 2020 at 21:41 BT643BT643 3,8555 gold badges38 silver badges56 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 5 +150

v-model gets and sets the value of whatever you pass to it. Since you want to edit a state property, as soon as you modify the <input>'s value it will try to change (a.k.a. mutate) the value of the state property. And that would break the immutability principle [1].
The solution here is to pass a puted property to v-model which uses a getter and a setter, where you tell the ponent where to get the value from and how to update it.

By default puted properties are a shorthand and only contain the getter. Basically,

puted: {
  name() {
    return this.$store.state.userProfile.name
  }
}

...can be written as:

puted: {
  name: {
    get() {
      return this.$store.state.userProfile.name
    }
  }
}

And what you need is to add a setter which mits the appropriate mutation so the state is updated:

puted: {
  ...mapState(["userProfile"]),
  name: {
    get() {
      return this.userProfile.name
    },
    set(val) {
      this.$store.mit('setUserProfile', {
        ...this.userProfile,
        name: val
      });
    }
  },
  title: {
    get() {
      return this.userProfile.title
    },
    set(val) {
      this.$store.mit('setUserProfile', {
        ...this.userProfile,
        title: val
      });
    }
  }
}

Computed setters are documented here.


[1] - the reason why you're using Vuex is because you don't want to allow any ponent to directly modify your data. Instead, you want them to mit mutations to the state so that every ponent using that data gets notified of the change. If you allowed v-model to change your data directly, you'd be breaking the immutability principle, so your state would cease to be the only source of truth.

Two things to take on count, first when you want to get the value from variable in the state crate getters (as good vuex practices):

Vuex file:

const store = new Vuex.Store({
  state: {
    userProfile: {},
    posts: [],
  },

  getters:{
    getUserProfile: (state) => state.userProfile
  }

Settigs.vue So, to acplish what you want you can load the variables in data() inside of the method mounted:

export default {
  data() {
    return {
      name: "",
      title: "",
      showSuccess: false,
    };
  },
  puted: {
    ...mapState(["getUserProfile"]),
  },

  mounted(){
   this.name = getUserProfile.name
  }

So, if you expect that the user will refresh the page without losing the data loaded, you can't use vuex alone, because when you refresh the page, vuex system restart too. If you want to maintain the data loaded after refresh the page use localstorage together with vuex or a similar solution.

Since it's just a timing thing:

I'd suggest you just bind your data value to a watcher on your state. Your ponent will simply listen everytime your state updates and will update your data accordingly.

export default {
  data() {
    return {
      name: "",
      title: "",
      showSuccess: false,
    };
  },
  puted: {
    ...mapState(["userProfile"]),
  },
  watch: {
    userProfile: {
      handler({ name, title }) {
        this.name = name;
        this.title = title;
      },
      deep: true, // deep is to listen to objects properly
      immediate: true // immediate so the watcher triggers right away when the ponent is mounted
    }
  },
  methods: {
    updateProfile() {
      this.$store.dispatch("updateProfile", {
        name: this.name !== "" ? this.name : this.userProfile.name,
        title: this.title !== "" ? this.title : this.userProfile.title,
      });
      
            /*  I just wouldn't reset the values here since they'll be updated in the watcher
      this.name = "";
      this.title = ""; */

      this.showSuccess = true;

      setTimeout(() => {
        this.showSuccess = false;
      }, 2000);
    },
  },
};
发布评论

评论列表(0)

  1. 暂无评论