Search code examples
firebasevue.jsvuex

Problem displaying uploaded image from firebase when refreshing page


Hello everyone and Happy New Year! Maybe someone knows why this can happen? There is a link to the image in the database, everything is also loaded in storage. And after adding the image, everything is also fine until the page is refreshed . Help someone, please. I didn’t find the answer to the question in Google.

import Vue from 'vue'
import Vuex from 'vuex'
import user from './user'
import cart from './cart'
import loading from './loading'
import * as fb from 'firebase/app'

Vue.use(Vuex)
class Product {
  constructor (title, price, description, ownerId, imageSrc = '', id = null) {
    this.title = title
    this.price = price
    this.description = description
    this.ownerId = ownerId
    this.imageSrc = imageSrc
    this.id = id
  }
}

export default function (/* { ssrContext } */) {
  const Store = new Vuex.Store({
    state: {
      amount: 1,
      products: [
      ]
    },
    getters: {
      addById (state) {
        return addId => {
          return state.products.find(product => product.id === addId)
        }
      },
      products (state) {
        return state.products
      }
    },
    actions: {
      async CREATE_PRODUCT ({ commit, getters }, payload) {
        commit('SET_LOADING', true)
        const image = payload.image

        try {
          const newProduct = new Product(payload.title, payload.price, payload.description, getters.user.id, '')
          const product = await fb.database().ref('products').push(newProduct)
          const imageExt = image.name.slice(image.name.lastIndexOf('.'))
          const fileData = await fb.storage().ref(`products/${product.key}.${imageExt}`).put(image)
          // const imageSrc = await fb.storage().ref().StorageReference(fileData.ref.fullPath).getDownloadUrl()
          const imageSrc = await fileData.ref.getStorage().getDownloadURL()
          await fb.database().ref('products').child(product.key).update({ imageSrc })
          commit('SET_LOADING', false)
          commit('CREATE_PRODUCT', {
            ...newProduct,
            id: product.key,
            imageSrc
          })
        } catch (error) {
          commit('SET_LOADING', false)
          throw error
        }
      },
      async fetchProducts ({ commit }) {
        commit('SET_LOADING', true)
        const resultProducts = []
        try {
          const fbVal = await fb.database().ref('products').once('value')
          const products = fbVal.val()
          Object.keys(products).forEach(key => {
            const product = products[key]
            resultProducts.push(
              new Product(product.title, product.price, product.description, product.imageSrc, product.ownerId, key)
            )
          })
          commit('LOAD_PRODUCTS', resultProducts)
          commit('SET_LOADING', false)
        } catch (error) {
          commit('SET_LOADING', false)
          throw error
        }
      }
    },
    mutations: {
      CREATE_PRODUCT (state, payload) {
        state.products.push(payload)
      },
      LOAD_PRODUCTS (state, payload) {
        state.products = payload
      }
    },
    modules: {
      user,
      loading,
      cart
    },

    strict: process.env.DEV
  })

  return Store
}

Solution

  • In your fetchProducts() function, when you initialize each product to add to the store, you have got the arguments out-of-order where imageSrc and ownerId are around the wrong way.

    new Product(product.title, product.price, product.description, product.imageSrc, product.ownerId, key)
    

    needs to change to

    new Product(product.title, product.price, product.description, product.ownerId, product.imageSrc, key)
    

    Other improvements

    In CREATE_PRODUCT, consider uploading the product data and image at the same time rather than one after the other.

    async CREATE_PRODUCT ({ commit, getters }, payload) {
      commit('SET_LOADING', true)
      const image = payload.image
    
      try {
        const newProduct = new Product(payload.title, payload.price, payload.description, getters.user.id, '')
        const imageExt = image.name.slice(image.name.lastIndexOf('.'))
        const productRef = fb.database().ref('products').push(); // generates push ID without uploading data
    
        const productDbPromise = productRef.set(newProduct)
        const productImagePromise = fb.storage().ref(`products/${productRef.key}.${imageExt}`).put(image)
          .then(fileData => fileData.ref.getStorage().getDownloadURL())
    
        const [fbSnapshot, imageSrc] = await Promise.all([productDbPromise, productImagePromise])
        await fbSnapshot.ref.update({ imageSrc })
    
        commit('SET_LOADING', false)
        commit('CREATE_PRODUCT', {
          ...newProduct,
          id: productRef.key,
          imageSrc
        })
      } catch (error) {
        commit('SET_LOADING', false)
        throw error
      }
    }
    

    In fetchProducts, iterate over the products tree just once (rather than using val() on the entire products tree, iterating for a list of all the keys, then iterating again for each value). This is less RAM intensive and will be snappier. In addition, you should consider paginating the products rather than return everything at once.

    async fetchProducts ({ commit }) {
      commit('SET_LOADING', true)
      try {
        const resultProducts = []
        const allProductsSnapshot = await fb.database().ref('products').once('value')
        allProductsSnapshot.forEach((productSnapshot) => {
          const product = productSnapshot.val();
          resultProducts.push(
            new Product(product.title, product.price, product.description, product.ownerId, product.imageSrc, productSnapshot.key)
          )
        });
    
        commit('LOAD_PRODUCTS', resultProducts)
        commit('SET_LOADING', false)
      } catch (error) {
        commit('SET_LOADING', false)
        throw error
      }
    }