1
votes

Built API with NodeJS, Express & MongoDB, used JWT and Cookies for user authentication. Fetched user data from API with axios service using store (vuex). Created auth.js in store folder, created fetchData action which GETs the data from backend (axios.get(apiRoute)) and sets the user to state. Wanted to do this using nuxtServerInit, so i craeted index.js file in store folder. Added empty state & actions. Action containts nuxtServerInit which uses dispatch() to call fetchData method in auth.js.

Yet after all of this, it doesn't work at all. For example: User is logged in, but account page is not rendering with user data (name, email, image etc.).

I tried returning a promise from fetchData action in auth.js, and it didn't work. Also i tried setting up fetchData action insite of the index.js file and calling dispatch directly on it.

store/auth.js

// Importing Files
import axios from 'axios';

// State
export const state = () => ({
    user: null
});

// Mutations
export const mutations = {
    SET_USER (store, data) {
        store.user = data
    },
    RESET_USER (store) {
        store.user = null
    }
};

// Actions
export const actions = {
    // Fetch User Account
    async fetchData ({ commit }) {
        try {
           const response = await axios.get('http://localhost:3000/api/v1/users/account');
            commit('SET_USER', response.data.doc);
            return response;
        } catch (err) {
            commit('RESET_USER');
        }
    }
};

store/index.js

// State
export const state = () => ({

});

// Actions
export const actions = {
    async nuxtServerInit({ dispatch }) {
        console.log('Testing');
        const res = dispatch('auth/fetchData');
        return res;
    }
};

components/Settings.vue

<template>
  <section class="data-block-wrap" v-if="user">
     <BlockHeader :blockHeaderName="`Welcome Back, ${user.name.split(' ')[0]}`" btnText="More Details" />
     <img :src="getPhotoUrl(user.photo)" alt="User Photo" class="user-data__image">
     <p class="user-data__short-bio">{{ user.shortBio }}</p>
  </section>
</template>

<script>
 export default {
    // Computed
    computed: {
        user() {
            return this.$store.state.auth.user;
        }
    }
    ...
 };
</script>

I expect to render user data properly on Vue components but currently it doesn't work at all. The render is static, no data from database / api showing.

EDIT / UPDATE

App renders user data properly when calling fetchData on created() hook in default.vue file ('Parent' file for all of the components).

default.vue

<template>
  <div class="container">
    <TopNav />
    <SideNav />
    <nuxt />
  </div>
</template>

// Importing Components
import TopNav from '@/components/navigation/TopNav';
import SideNav from '@/components/navigation/SideNav';
import axios from 'axios';

import { mapActions } from 'vuex';

export default {
  components: {
    TopNav,
    SideNav
  },
  methods: {
  // Map Actions
  ...mapActions('auth', ['fetchData']),
    async checkUser() {
      const user = await this.fetchData();
    },
  },
   // Lifecycle Method - Created
   created() {
    this.checkUser();
  }
}
</script>
2
Have you checked whether or not fetchData is throwing, and calling commit('RESET_USER')? If that's working correctly, can you post an example of your component using the data? - HMilbradt
Yes, commit('RESET_USER') is being called properly, because fetchData throws error every single time it's being called on index.js with dispatch method. Post updated with component using user data. - AndreasDEV
From docs: 'Note: Asynchronous nuxtServerInit actions must return a Promise or leverage async/await to allow the nuxt server to wait on them.' Does await dispatch('auth/fetchData'); in nuxtServerInit help? - c6p
@c6p already tried async await approach but no luck with it. Doesn't affect it in any way. - AndreasDEV

2 Answers

1
votes

It seems that something very interesting is happening here. The problem is calling axios.get('http://localhost:3000/api/v1/users/account') from within nuxtServerInit().

This is causing what is essentially an infinite recursion. nuxtServerInit makes a call to http://localhost:3000, which hits the same server, runs nuxtServerInit again, and calls http://localhost:3000, and so on until the javascript heap is out of memory.

Instead of using nuxtServerInit for this, use the fetch method:

The fetch method is used to fill the store before rendering the page, it's like the asyncData method except it doesn't set the component data.

Note: You do not have access to the Nuxt component in fetch, so you must use the context object instead of "this"

// inside your page component
export default {
  fetch (context) {
    return context.store.dispatch('auth/fetchData');
  }
}

As a general rule:

  • Use fetch to fill store data on the server or client
  • Use asyncData to fill component data on the server or client
  • Use nuxtServerInit for things like setting up the store with values on the request object, like sessions, headers, cookies, etc, which is only required server side
1
votes
    The solution to this question is to use the NuxtServerInt Action this way inside your store.js
    
    1. you will need to run  npm install cookieparser and npm install js-cookie
    
    const cookieparser = process.server ? require('cookieparser') : undefined
    
    export const state = () => {
      return {
        auth: null,
      }
    }
    export const mutations = {
      SET_AUTH(state, auth) {
        state.auth = auth
      },
     
    }
    export const actions = {
      nuxtServerInit({ commit }, { req }) {
        let auth = null
        if (req.headers.cookie) {
          try {
            const parsed = cookieparser.parse(req.headers.cookie)
            auth = parsed.auth
          } catch (err) {
            console.log('error', err)
          }
        }
        commit('SET_AUTH', auth)
      },
    }


Then in your login page component, you call your backend API, just like this 

import AuthServices from '@/ApiServices/AuthServices.js'
import swal from 'sweetalert'
const Cookie = process.client ? require('js-cookie') : undefined

 async onSubmit() {
      try {
        
        const body = {
          email: this.email,
          password: this.password,
        }

        const res = await AuthServices.loginUrl(body)
        console.log('res', res)
        console.log('res', res.data.message)
        setTimeout(() => {
          // we simulate the async request with timeout.
          const auth = {
            accessToken: res.data.payload.token, // from your api call, you get the user token 
            userData: res.data.payload.user,
          }
          swal('Logged in', `${res.data.message}`, 'success')

          this.email = this.password = ''

          this.$refs.loginForm.reset()
          this.$store.commit('setAuth', auth) // mutating to store for client rendering
          Cookie.set('auth', auth) // saving token in cookie for server rendering
          this.$router.push('/')
        }, 1000)
      } catch (error) {
        console.log('error', error)
        swal('Error!', `${error.message}`, 'error')
      }
    },


your AuthServices.js looks like this

import axios from 'axios'

const apiClient = axios.create({
  baseURL: `http://localhost:3000`,
})

export default {
  loginUrl(body) {
    return apiClient.post('/login', body, {
      headers: {
        'Content-Type': 'application/json',
      },
    })
  }
}




then you get the user data using computed in the navbar or say dashboard e.g to say Hi,Xavier

inside where you want place the user data, just add this
<template>
  <section>
     <p class="firtname_data">Hi, {{ user.firstnam }}</p>
  </section>
</template>

<script>
 export default {
    // Computed
    computed: {
    user() {
      return this.$store.state.auth.userData
    }
    ...
 };
</script>



Hope this help... it worked for me