Ответы пользователя по тегу Vuex
  • Как обновить данные в vuex state?

    0xD34F
    @0xD34F Куратор тега Vue.js
    data(){
      return{
        Item: {
          ...

    v-model="item.name"

    Ну и как там у вас имя свойства начинается - с большой буквы или маленькой? Вы определитесь.

    ADD_TO_PRODUCTS({commit}, item){
        commit('SET_PRODUCT_TO_STATE', item)
    },

    methods:{
      ...mapActions([
          'ADD_TO_PRODUCTS',
      ]),

    @click="ADD_TO_PRODUCTS"

    В экшене ожидается параметр, но вы ничего не передаёте. Точнее, не передаёте в явном виде - передаётся объект события клика. А надо item (Item?).

    После того, как исправите косяки выше, у вас, вероятно, вылезет ещё один. С ним разбираться будете здесь.
    Ответ написан
    Комментировать
  • Как загрузить данные следующей страницы?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Помимо массива с данными держите в стейте ссылку на следующую страницу:

    state: {
      pokemons: [],
      next: 'https://pokeapi.co/api/v2/pokemon',
    },

    Используйте её для запроса данных, при получении ответа не забывайте сохранять новую ссылку:

    actions: {
      async fetchPokemons({ state, commit }) {
        const data = await fetch(state.next).then(r => r.json());
    
        commit('setPokemons', {
          pokemons: data.results,
          next: data.next,
        });
      }
    },

    Также можете использовать значение этой ссылки для блокирования кнопки, по нажатию на которую инициируется загрузка следующей порции данных, если больше загружать нечего:

    <button
      :disabled="!$store.state.next"
      @click="$store.dispatch('fetchPokemons')"
    >
      NEXT
    </button>

    https://jsfiddle.net/yb2seq6m/

    Но вообще, никакая "следующая" страница тут не нужна. Храните номер текущей страницы и количество элементов на странице, исходя из этих данных самостоятельно вычисляйте значение параметра offset. Когда понадобится следующая - делаете +1 к текущей. Например.
    Ответ написан
    Комментировать
  • Как из компонента обновлять объект в хранилище?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Когда в хранилище появится новое свойство, будете вручную его добавлять в компонент? Хранилище доступно в data (если, конечно, не будете делать её стрелочной), так что можно просто скопировать объект; в наблюдателе вместо проверок конкретных свойств можно сделать цикл по всем:

    rangeValues: { ...this.$store.state.CarSetup },

    Object.entries(newValue).forEach(([ k, v ]) => {
      if (v !== oldValue[k]) {
        this.$store.commit('setCarInfo', { to: k, value: v });
      }
    });


    А вообще, не надо ни копии объекта из хранилища, ни наблюдателя. Вырезаете returnValue, свойство rangeValues из обычного делаете вычисляемым:

    computed: {
      rangeValues() {
        return new Proxy(this.$store.state.carSetup, {
          set: (target, key, val) => {
            this.$store.commit('setCarInfo', { [key]: val });
            return true;
          },
        });
      },
    },

    Мутацию переписываете следующим образом:

    setCarInfo: (state, payload) => Object.assign(state.carSetup, payload),
    Ответ написан
  • Почему не изменяется значение в Vuex?

    0xD34F
    @0xD34F Куратор тега Vue.js
    По клику на кнопку вызывается мутация, в которою передаётся id.

    Эти слова следовало проиллюстрировать кодом. Потому что сама по себе показанная вами мутация... дичь дикая конечно, но рабочая, проблема где-то в другом месте - может, неправильно вызываете мутацию, или не ту, или при вызове передаёте несуществующий id.

    Я так понимаю, потому что метод возвращает только false/true, а не измененный item.

    Не понимаете. Какая разница, что вы там из мутации возвращаете? Да никакой.

    Как примерно должен был выглядеть ваш код. Смотрите, сравнивайте с тем, что у вас реально есть.

    UPD. Вынесено из комментариев:

    id передается, проверял через консоль

    ...mapMutations(['chekedItem', 'deleteItem']),
    itemSucces(id) {
      this.chekedItem({id});
    },

    Правда проверяли? И как же вы ухитрились при этом не заметить, что передаёте объект вместо числа? Кстати, в данной ситуации можно и объект передавать - сам item, тогда его не придётся искать в мутации:

    chekedItem: (state, item) => item.checked = !item.checked,

    Зачем метод дополнительный создавать? - вызывайте сразу chekedItem, а itemSucces удалите.
    Ответ написан
  • Как заставить работать пагинацию?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Пусть в сторе кроме собственно массива данных также хранятся текущая страница и общее количество страниц; действие fetchCharacters - пусть загружает не дефолтную первую страницу, а какую будет указано:

    state: {
      characters: [],
      page: 0,
      pages: 0,
    },
    mutations: {
      setCharacters: (state, { characters, pages, page }) =>
        Object.assign(state, { characters, pages, page }),
    },
    actions: {
      async fetchCharacters({ commit }, page = 1) {
        try {
          const { data: { info, results } } = await axios.get(`${BASE_URL}?page=${page}`);
          commit('setCharacters', {
            page,
            pages: info.pages,
            characters: results,
          });
        } catch (e) {
          console.error(e);
        }
      },
    },

    В компоненте, где используется пагинация, делаем вычисляемое свойство, которое будет отвечать за текущую страницу - геттер достаёт значение из стора, сеттер вызывает действие fetchCharacters:

    computed: {
      currentPage: {
        get() {
          return this.$store.state.page;
        },
        set(page) {
          this.$store.dispatch('fetchCharacters', page);
        },
      },
    },

    Привязываем это вычисляемое свойство к экземпляру компонента пагинации:

    <el-pagination
      v-model:current-page="currentPage"
      :page-count="$store.state.pages"
      layout="prev, pager, next"
      background
    />

    https://jsfiddle.net/qbjc9onm/
    Ответ написан
    Комментировать
  • Как правильно выводить данные с API в Vuex 4?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Где вызов экшена GET_CHARACTERS? Не вижу. Надо добавить.

    let data = await axios.get(baseURL);
    context.commit('SET_CHARACTERS', data)

    Здесь вы передаёте в мутацию объект ответа целиком - не только данные, но ещё и статус, заголовки и т.д. Кроме того, судя по дефолтному значению characters, вы желаете получить массив, а возвращается объект, содержащий массив в качестве значения свойства results. Так что пусть будет

    const response = await axios.get(baseURL);
    context.commit('SET_CHARACTERS', response.data.results);

    <div>{{ $store.state.characters }}</div>

    Раз выводите массив - выводите его поэлементно:

    <div v-for="n in $store.state.characters">
      {{ n.name }}
    </div>

    UPD. https://jsfiddle.net/o3wszLdv/
    Ответ написан
  • Как в дочернем компоненте узнать, что закончилась асинхронная загрузка данных?

    0xD34F
    @0xD34F Куратор тега Vue.js
    В App.vue я запрашиваю все продукты из БД

    Если продукты должны быть доступны везде, может, тогда стоит сначала их получить, а потом уже инициализировать приложение:

    store.dispatch('GET_PRODUCTS').then(() => {
      new Vue({ ... });
    });

    ??

    Если же отвечать непосредственно на спрошенное - установите наблюдение за значением в хранилище:

    watch: {
      '$store.state.products': {
        immediate: true,
        handler(products) {
          if (products) {
            // можно что-то сделать
          }
        },
      },
    },
    Ответ написан
    1 комментарий
  • Как изменять вложенные комментарии (объекты) в vuex store?

    0xD34F
    @0xD34F
    Передавайте в мутацию вместо id сам объект комментария - тогда ничего искать будет не надо.

    Или сделайте рекурсивную функцию поиска, что-то вроде

    const find = (arr, id) =>
      (Array.isArray(arr) ? arr : []).reduce((found, n) =>
        found || (n.id === id ? n : find(n.replies, id))
      , null);
    Ответ написан
    3 комментария
  • Как во Vuex внутри action вызвать другой action?

    0xD34F
    @0xD34F
    // Вот так не работает почему-то:
    // this.GET_HERITAGEOBJECTS_FROM_DB()

    "Почему-то"? А кто вам обещал, что так работать будет? Action'ы вызываются через dispatch, если не объявили action как стрелочную функцию, то dispatch будет доступен через this:

    this.dispatch('GET_HERITAGEOBJECTS_FROM_DB');

    Или (читаем документацию внимательнее) он всегда доступен как свойство первого параметра action'а.

    // Vue ругается: error   Unreachable code   no-unreachable

    Во-первых - не vue, а линтер. Во-вторых, это не имеет никакого отношения к вашей проблеме, будет так ругаться на любой код в этом месте. "Unreachable code" - недостижимый код. Этот код никогда не выполнится, потому что он расположен после return.
    Ответ написан
    Комментировать
  • Как после добавления объекта в стор избежать ошибки "do not mutate vuex store state outside mutation handlers"?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Error: [vuex] do not mutate vuex store state outside mutation handlers.

    Ну да, как и должно быть. Вы же помещаете в массив, находящийся в хранилище, ссылку на объект, с которым работаете в компоненте с формой.

    Добавляя объект в хранилище, создавайте копию:

    this.$store.commit('vehicles/addVehicle', { ...this.vehicle });
    // или
    addVehicle: (state, payload) => state.vehicles.push({ ...payload }),


    Или заменяйте оригинал. Добавляете в компонент метод, создающий объект с дефолтными значениями свойств:

    methods: {
      createNewVehicle: () => ({
        name: '',
        description: '',
        rent: '',
        type: 'custom',
      }),
      ...

    Применяете его при создании объекта с данными экземпляра компонента и после вызова мутации:

    data() {
      return {
        vehicle: this.createNewVehicle(),
      };
    },

    this.$store.commit('vehicles/addVehicle', this.vehicle);
    this.vehicle = this.createNewVehicle();
    Ответ написан
    1 комментарий
  • Как сделать пагинацию через Vuex?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Хранилище

    Какие свойства должны быть в стейте - номер текущей страницы; количество элементов на странице; общее количество элементов; данные текущей страницы; флаг, сигнализирующий о том, что в данный момент осуществляется загрузка данных (первые четыре у вас уже есть, последнее почему-то находится в компоненте - надо переместить):

    state: {
      page: 0,
      perPage: 5,
      total: 0,
      posts: [],
      loading: false,
    },

    Понадобится геттер, вычисляющий количество страниц:

    getters: {
      numPages: state => Math.ceil(state.total / state.perPage),
    },

    Что и как будет обновляться в стейте:

    mutations: {
      updateLoading: (state, loading) => state.loading = loading,
      updatePosts: (state, { posts, total, page }) => Object.assign(state, { posts, total, page }),
    },

    Загрузка данных - до запроса устанавливаете loading в true, после переключаете обратно в false; запрос, сохранение результатов - тут, думаю, всё понятно:

    actions: {
      async fetchPosts({ state, commit }, page) {
        commit('updateLoading', true);
    
        const start = (page - 1) * state.perPage;
        const end = page * state.perPage;
        const url = `https://jsonplaceholder.typicode.com/posts?_start=${start}&_end=${end}`;
    
        try {
          const response = await fetch(url);
          const posts = await response.json();
          const total = response.headers.get('x-total-count');
          commit('updatePosts', { posts, total, page });
        } catch (e) {
          console.error(e);
        }
    
        commit('updateLoading', false);
      },
    },

    Компонент пагинации

    Получает два параметра - номер текущей страницы и количество страниц:

    props: [ 'page', 'numPages' ],

    Имеет методы для перехода к конкретной странице (отправляет номер новой страницы в родительский компонент - тому виднее, что с ним делать) и к странице относительно текущей:

    methods: {
      goTo(page) {
        this.$emit('paginate', page);
      },
      next(step) {
        this.goTo(this.page + step);
      },
    },

    Чтобы не давать пользователю переходить к несуществующим страницам, сделаем вычисляемые свойства, указывающие, является ли текущая страница первой/последней,...

    computed: {
      isFirst() {
        return this.page <= 1;
      },
      isLast() {
        return this.page >= this.numPages;
      },
    },

    ...и используем эти свойства для блокировки кнопок перехода на предыдущую/следующую/первую/последнюю страницы:

    <button @click="goTo(1)" :disabled="isFirst">&lt;&lt;</button>
    <button @click="next(-1)" :disabled="isFirst">&lt;</button>
    {{ page }} / {{ numPages }}
    <button @click="next(+1)" :disabled="isLast">&gt;</button>
    <button @click="goTo(numPages)" :disabled="isLast">&gt;&gt;</button>

    Ну и добавим поддержку директивы v-model:

    model: {
      prop: 'page',
      event: 'paginate',
    },


    Как всем этим пользоваться

    В компоненте, из которого планируется осуществление пагинации, делаем вычисляемое свойство, значением которого является номер текущей страницы из хранилища, а при установке нового значения будет вызываться действие, загружающее данные:

    computed: {
      page: {
        get() {
          return this.$store.state.page;
        },
        set(page) {
          this.$store.dispatch('fetchPosts', page);
        },
      },

    Привязываем через v-model это свойство к экземпляру компонента пагинации (ну и количество страниц не забываем ему передать):

    <компонент-пагинации
      v-model="page"
      :num-pages="$store.getters.numPages"
    />

    Не забываем указать, какую страницу хотим видеть по умолчанию:

    created() {
      this.page = 1;
    },

    Отображение данных:

    <div v-if="$store.state.loading">данные загружаются, ждите</div>
    <компонент-для-отображения-данных v-else :данные="$store.state.posts" />


    https://jsfiddle.net/08hno57q/
    Ответ написан
    Комментировать
  • Как сделать геттер-функцию?

    0xD34F
    @0xD34F
    Зачем функция? Делаем геттер, представляющий посты, сгруппированные по категориям:

    getters: {
      postsByCatId: ({ posts }) =>
        posts.reduce((acc, n) => (
          (acc[n.catId] = acc[n.catId] || []).push(n),
          acc
        ), {}),
    },

    Соответственно, в компоненте добавляем свойство, представляющее id категории, и вычисляемое свойство, представляющее массив постов. Можно оформить миксин, если подобное нужно в разных компонентах:

    const postsMixin = {
      data: () => ({
        catId: null,
      }),
      computed: {
        posts() {
          return this.$store.getters.postsByCatId[this.catId] || [];
        },
      },
    };
    Ответ написан
    2 комментария
  • Как получить данные из api используя цикл в vuex?

    0xD34F
    @0xD34F
    • Вызов Axios.get - лишние кавычки (внутренние)
    • Пытаетесь запрашивать несуществующие данные - на первой итерации цикла id равен нулю, объекта с таким id указанный вами api не возвращает
    • Вызов мутации - вместо response следует передавать response.data
    • В самой мутации - изменяете одно свойство, а в стейте у вас какое-то другое
    • Опять же, в мутации - выполняется присваивание, хотя, судя по коду в экшене (цикл), результаты должны складываться в массив (то есть, необходимо изменить или экшен, или мутацию - в первом случае следует собирать массив полностью, и только потом делать один общий коммит, во втором случае вместо присваивания должен выполняться push)

    UPD. Исправляем:

    mutations: {
      setTodos: (state, todos) => state.todos = todos,
    },
    actions: {
      async getTodos({ commit }) {
        const results = [];
    
        for (let i = 1; i <= 10; i++) {
          try {
            const { data } = await axios.get(`https://jsonplaceholder.typicode.com/todos/${i}`);
            results.push(data);
          } catch (e) {
            console.log(e);
          }
        }
    
        commit('setTodos', results);
      },
    },

    <ul>
      <li v-for="n in $store.state.todos" :key="n.id">
        <div>#{{ n.id }}</div>
        <div>{{ n.title }}</div>
      </li>
    </ul>
    Ответ написан
    6 комментариев
  • Как каждому из экземпляров Vue назначить отдельный Store?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Если воспользуетесь конструктором Vuex.Store более одного раза, вас за это в тюрьму не посадят. Так что вам ничего не мешает создать несколько разных сторов, свой для каждого экземпляра Vue.

    А если их структура должна быть идентична - сделайте функцию, которая будет создавать стор и вместо

    new Vue({
      store: store,
      ...

    будет

    new Vue({
      store: createStore(),
      ...

    https://jsfiddle.net/25L08xj1/
    Ответ написан
    Комментировать
  • Как при клике на кнопку в блоке открывать модальное окно/выезжающий сайдбар с данными того блока, в котором находиться кнопка?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Добавляете в стейт свойство, которое будет представлять выбранный блок:

    state: {
      opened: null,
      ...

    В экземпляры компонента Trigger передавайте соответствующие им блоки:

    <Trigger :block="block">

    props: [ 'block' ],

    Также, Trigger должен знать, какой блок открыт:

    computed: mapState([ 'opened' ]),

    Чтобы можно было назначить класс кнопке:

    :class="{ 'active' : block === opened }"

    При клике на которую следует передавать блок в мутацию:

    @click="toggleNav(block)"

    Мутация же будет устанавливать значение выбранного блока:

    toggleNav(state, block) {
      state.opened = state.opened === block ? null : block;
    },

    Это если оно действительно toggle. Если просто открытие - тогда достаточно state.opened = block (название мутации в этом случае конечно следует поменять).

    Мутация, отвечающая за закрытие, будет выглядеть так:

    closeSidebarPanel(state) {
      state.opened = null;
    },

    Геттер isNavOpen тоже изменится:

    isPanelOpen(state) {
      return !!state.opened;
    },

    Ну и наконец, можно что-нибудь вывести в Sidebar, например:

    <span v-if="isPanelOpen">{{ $store.state.opened.bName }}</span>

    Но если Sidebar предназначен конкретно для этих блоков, наверное, разумнее будет, чтобы он сам знал, кто сейчас открыт. В Sidebar'е цепляете opened к слоту:

    <slot :block="$store.state.opened"></slot>

    Соответственно, в родительском компоненте:

    <template #default="{ block }">
      <div class="sidebar-panel-settings">
        <div class="setting-block">
          {{ block.bName }}
        </div>
      </div>
    </template>
    Ответ написан
    1 комментарий
  • Как реализовать обновление состояния в хранилище в реальном времени?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Хранилище:

    state: {
      date: null,
      interval: null,
    },
    mutations: {
      update(state) {
        state.date = new Date();
      },
      start(state) {
        if (!state.interval) {
          state.interval = setInterval(this.commit, 1000, 'update');
        }
      },
      stop(state) {
        if (state.interval) {
          clearInterval(state.interval);
          state.interval = null;
        }
      },
    },

    Компонент:

    computed: {
      timerActive() {
        return !!this.$store.state.interval;
      },
      timeStr() {
        const { date } = this.$store.state;
        return date instanceof Date
          ? [ 'getHours', 'getMinutes', 'getSeconds' ]
              .map(n => `${date[n]()}`.padStart(2, 0))
              .join(':')
          : 'ВРЕМЕНИ НЕТ';
      },
    },
    created() {
      this.$store.commit('start');
    },

    <button @click="$store.commit('start')" :disabled="timerActive">start</button>
    <button @click="$store.commit('stop')" :disabled="!timerActive">stop</button>
    <div>{{ timeStr }}</div>
    Ответ написан
  • Как сохранять выбранные пользователем элементы каталога?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Элементы games должны иметь уникальное свойство, которое бы позволяло их идентифицировать, id. Если такого нет - срочно добавляйте. Сохранять в localStorage следует эти id. Так что добавим в стейт соответствующий массив:

    state: {
      wishlistIds: [],
      ...

    И укажем его в настройках persistedstate (да, можно сохранять не весь стейт, а только некоторые элементы):

    plugins: [
      createPersistedState({
        paths: [ 'wishlistIds' ],
      }),
      ...

    Массив wishlist - из стейта переносим в геттеры:

    getters: {
      wishlist: state => state.games.filter(n => state.wishlistIds.includes(n.id)),
      ...

    Добавление/удаление, соответственно:

    mutations: {
      addGame(state, gameId) {
        if (!state.wishlistIds.includes(gameId)) {
          state.wishlistIds.push(gameId);
        }
      },
      removeGame(state, gameId) {
        const index = state.wishlistIds.indexOf(gameId);
        if (index !== -1) {
          state.wishlistIds.splice(index, 1);
        }
      },
      ...

    В компонент, отображающий элементы games, добавим вычисляемое свойство liked, которое будет указывать, находится ли текущий элемент в wishlist и управлять добавлением/удалением:

    computed: {
      liked: {
        get() {
          return this.$store.state.wishlistIds.includes(this.game.id);
        },
        set(val) {
          this.$store.commit(val ? 'addGame' : 'removeGame', this.game.id);
        },
      },
      ...

    <button @click="liked = !liked" :class="{ liked }" class="like"></button>

    Ну а свойство isLiked и метод putLike - это мусор, их следует вырезать.
    Ответ написан
    Комментировать