4 votes

Vuex charge les données d'un formulaire existant de l'état à l'édition

J'ai suivi ce tutoriel pour essayer d'apprendre Vue, j'ai terminé et ça fonctionne, mais j'essaie de faire un changement avec lequel j'ai du mal.

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

Il y a donc une page "paramètres" qui contient le profil de l'utilisateur (il peut modifier son nom, etc.). Lorsque cette page "paramètres" / "profil" est chargée, je veux que le formulaire charge les données existantes afin que l'utilisateur puisse les modifier et cliquer sur "enregistrer".

Il se charge actuellement comme un espace réservé avec :placeholder="userProfile.name" - Je veux qu'il remplisse le formulaire avec la valeur réelle au lieu de l'avoir en tant qu'espace réservé.

J'ai l'impression que cela devrait être ridiculement simple à faire, mais je n'arrive pas à le faire fonctionner de manière élégante.

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,
    };
  },
  computed: {
    ...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>

J'ai essayé de modifier la section des données comme suit, ce qui fonctionne lorsque je quitte la page et que j'y reviens, mais si je rafraîchis la page (F5), les champs restent vides jusqu'à ce que je quitte la page et que j'y revienne.

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

Et voici mon magasin si vous avez besoin de le voir :

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.commit("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({ commit }) {
      await fb.auth.signOut();

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

      // set user profile in state
      commit("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,
        comments: 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 comments by user
      const commentDocs = await fb.commentsCollection
        .where("userId", "==", userId)
        .get();
      commentDocs.forEach((doc) => {
        fb.commentsCollection.doc(doc.id).update({
          userName: user.name,
        });
      });
    },
  },
  modules: {},
});

export default store;

EDIT

J'aurais dû mentionner que ces données sont chargées dans l'état à partir d'un Firebase Firestore. Il semble que ce soit juste un problème de timing, les données ne sont pas tout à fait chargées au moment où il met en place le data() sur le composant - j'ai ajouté quelques logs de console.

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

Encore une fois, je ne connais pas assez Vue pour savoir comment modifier au mieux cet ordre

5voto

Andrei Gheorghiu Points 3898

v-model obtient y ensembles la valeur de ce que vous lui transmettez. Puisque vous voulez modifier une propriété d'état, dès que vous modifiez la propriété <input> qu'il essaiera de modifier (a.k.a. muter ) la valeur des biens de l'État. Et cela romprait le principe d'immutabilité [1] .
La solution consiste à transmettre une propriété calculée à v-model qui utilise un getter et un setter, où vous indiquez au composant où obtenir la valeur et comment la mettre à jour.

Par défaut computed sont une abréviation et ne contiennent que le getter. En principe,

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

...peut s'écrire comme suit :

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

Et ce dont vous avez besoin, c'est d'ajouter un setter qui engage la mutation appropriée pour que l'état soit mis à jour :

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

Les fixateurs calculés sont documentés aquí .


[1] - la raison pour laquelle vous utilisez Vuex est que vous ne voulez pas permettre à un composant d'utiliser directement la fonction modifier vos données . Au contraire, vous voulez qu'ils livrent les mutations à l'état de sorte que chaque composant utilisant ces données soit notifié du changement. Si vous autorisez v-model de modifier directement vos données, vous enfreindriez le principe d'immuabilité, et votre état cesserait donc d'être la seule source de vérité.

0voto

Pierre Burton Points 714

Puisque ce n'est qu'une question de temps :

Je vous suggère de lier votre valeur de données à un observateur sur votre état. Votre composant écoutera simplement chaque fois que votre état sera mis à jour et mettra à jour vos données en conséquence.

export default {
  data() {
    return {
      name: "",
      title: "",
      showSuccess: false,
    };
  },
  computed: {
    ...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 component 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);
    },
  },
};

0voto

Straight Coding Points 146

Deux choses à prendre en compte, tout d'abord lorsque vous voulez obtenir la valeur de la variable dans les getters de la caisse d'état (comme le veut la bonne pratique de vuex) :

Fichier Vuex :

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

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

Settigs.vue Donc, pour accomplir ce que vous voulez, vous pouvez charger les variables dans data() à l'intérieur de la méthode mounted :

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

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

Ainsi, si vous espérez que l'utilisateur rafraîchira la page sans perdre les données chargées, vous ne pouvez pas utiliser vuex seul, car lorsque vous rafraîchissez la page, le système vuex redémarre également. Si vous souhaitez conserver les données chargées après le rafraîchissement de la page, utilisez localstorage avec vuex ou une solution similaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X