Nuxt async not work on page reload - firebase - firebase

I have a issue with asyncData() when i refresh the page. If I navigate from list to single item, it work, but if i reload the page i will see an empty object.
In my page i have this :
<template>
<div>
{{ getItem}}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data: () => ({
}),
computed: {
...mapState([
'single_item'
]),
getItem() {
return this.single_item
}
},
async asyncData({app,route, params,store}) {
let type = 'posts'
let id = params.id
return store.dispatch('fetchFirebaseSingle', {type,id })
}
}
</script>
in store.js
import { db } from '~/plugins/firebase'
const actions = {
....
async fetchFirebaseSingle({commit}, {type, id}) {
try {
console.log('fetchFirebaseSingle', type)
const docRef = await db.collection(type).doc(id)
docRef.get()
.then((doc) => {
if (doc.exists) {
const file = doc.data()
commit('SET_PAGE_SINGLE', file)
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch((error) => {
console.log("Error getting document:", error);
});
} catch (e) {
console.log("Error getting document:", e);
}
},
}
const mutations = {
...
// Set Single Item
SET_PAGE_SINGLE ( state, single_item) {
state.single_item = single_item
},
},
const state = () => ({
single_item : {},
})
I tryed also to call directly from this page the database, but i have same issue. Did someone get similar issue with vuex and firebase or asyncData ?
Thanks

Nothing special here, asyncData is not supposed to work on page reload or a refesh (F5) but only with page transitions.
Unlike fetch, the promise returned by the asyncData hook is resolved during route transition
You could use the fetch() hook if you don't mind a non-blocking loading.
More info here: https://nuxtjs.org/docs/features/data-fetching#data-fetching

Related

Nuxt3 and Pinia: how to save async API data to the store

I created a Pinia store file to retrieve invoices information from the Node.js API I created available on a public API address
import { mande } from "mande";
import { acceptHMRUpdate, defineStore } from "pinia";
import { useUsersStore } from "./user";
const api = mande("http://xxx.xxx.xxx.xxx/"); // hiding the IP address
const usersStore = useUsersStore();
await usersStore.signIn("test#gmail.com", "password");
api.options.headers.Authorization = "Bearer " + usersStore.getAccessToken;
export const useInvoicesStore = defineStore("invoices", {
state: () => ({
invoices: <any>[] || [],
invoice: null,
loading: false,
}),
getters: {
getInvoices: (state) => state.invoices,
getInvoice: (state) => state.invoice,
},
actions: {
async fetchInvoices() {
this.invoices = [];
this.loading = true;
try {
this.invoices = (await api.get("invoices")) as any[];
} catch (error) {
console.log(error);
} finally {
this.loading = false;
}
},
async fetchInvoice(id: string) {
this.invoice = null;
this.loading = true;
try {
this.invoice = (await api.get(`invoices/${id}`)) as any;
} catch (error) {
console.log(error);
} finally {
this.loading = false;
}
},
async createInvoice(invoice: any) {
this.loading = true;
try {
await api.post("invoices", invoice);
} catch (error) {
console.log(error);
} finally {
this.loading = false;
}
},
async updateInvoice(id: string, invoice: any) {
this.loading = true;
try {
await api.patch(`invoices/${id}`, invoice);
} catch (error) {
console.log(error);
} finally {
this.loading = false;
}
},
},
});
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUsersStore, import.meta.hot));
}
I use the store in a Nuxt3 page
<script setup>
const store = useInvoicesStore();
definePageMeta({
layout: "app",
});
let invoices = [];
await store.fetchInvoices();
invoices = store.getInvoices;
</script>
<template>
<div>
<main>
{{ invoices }}
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
<AppInvoiceList :invoices="invoices" />
</div>
</main>
</div>
</template>
I print the entire JSON (invoices) on the UI to understand whether the information is fetched from the server. What happens is that, once I hit reload (F5), for a split second the data appears on the screen. After that, the array is empty and the store as well.
How can I correctly save the data coming from the API in the Pinia store?
This is not the purpose of a Pinia store, it does not give out of the box persisted store states, it is only for providing central state management during the uninterrupted life span of the PWA.
There are two ways I can think of to persist the central state between reloads.
Option 1
Use $subscribe to save Pinia states to the browsers localStorage or indexDB using them as a cache, then on first load check localStorage for anything to restore back to the Pinia state else query the backend, you will need to consider a cache timeout mechanism.
https://pinia.vuejs.org/core-concepts/state.html#subscribing-to-the-state
There may be a persisted state pinia plugin available to do this for you, try searching for a solution.
Option 2
Service worker API - You may not need panini at all.
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
https://developer.mozilla.org/en-US/docs/Web/API/Cache

How to create a reactive statement in Svelte with Firebase's onSnapshot?

I'm learning Svelte for the past 4 days and I'm trying to integrate it with Firebase.
I need to listen to a document named after the user's uid after the user logged in which I saved in a writable name userStore.
Note: My background was React and this can be done easily with useEffect
I need a way to call unsubscribe in the onDestroy statement... How can I do that?
onDestroy(() => {
unsubscribe();
});
This is my current code:
$: if ($userStore)
onSnapshot(doc(db, 'userInfo', $userStore.uid), (doc) => {
if (doc.data()) {
console.log(doc.data());
userInfoStore.set(doc.data() as UserInfo);
}
});
I think onSnapshot() returns unsubscribe, so it should work like this
<script>
let unsubscribe
onDestroy(() => {
if(unsubscribe) unsubscribe();
});
$: if ($userStore) {
unsubscribe = onSnapshot(doc(db, 'userInfo', $userStore.uid), (doc) => {
if (doc.data()) {
console.log(doc.data());
userInfoStore.set(doc.data() as UserInfo);
}
});
}
</script>
Is the component destroyed when the user logs out? Because the unsubcription should be called if the user logs out? I think in a component might not be the best place to handle the logic. This would be a way via a custom store
userInfoStore.js
export const userInfoStore = (() => {
const initialValue = {}
const {subscribe, set} = writable(initialValue)
let unsubSnapshot
return {
subscribe,
startListener(uid) {
unsubSnapshot = onSnapshot(doc(db, 'userInfo', uid), (doc) => {
if (doc.data()) {
console.log(doc.data());
set(doc.data() as UserInfo);
}
});
},
stopListener() {
if(unsubSnapshot) unsubSnapshot()
}
}
})();
auth.js
onAuthStateChanged(auth, user => {
if(user) {
userStore.set(user)
userInfoStore.startListener(user.uid)
}else {
userInfoStore.stopListener()
}
})
App.svelte (main component)
Don't know how important that is or if the listener is stopped anyway when the page is closed
<script>
import {onDestroy} from 'svelte'
import {userInfoStore} './userInfoStore'
onDestroy(() => {
userInfoStore.stopListener()
});
</script>

How to display 404 error page with react-query in next-ssr

I am using react query as stated in the doc
But, I am not sure how to return {notFound: true} from getServerSideProps. Could anyone help me how to handle this?
My code ins getServerSideProps is:
const queryClient = new QueryClient();
await queryClient.prefetchQuery('amenities', () => getAmenities(params?.id as string));
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
Now, what I want to do is, if the response is 404, then return 404 from getServerSideProps.
Update: I was not able to find any solution so removed react query in getServerSideProps. Now I'm fetching the data normally.
what you can do is do it on the client side.
const { data, isLoading, isError } = useQuery(
["amenities", router.query.id],
() => getAmenities(router.query.id)
)
if(isError) router.push('404');
You can do it in the server side too
const queryClient = new QueryClient();
const data = await queryClient.prefetchQuery('amenities', () => getAmenities(params?.id as string));
if (!data) {
return {
notFound: true,
}
}
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};

Infinite loop on Redux saga yield call using Axios with JWT tokens

I have been trying to obtain data using Axios through Redux-saga using Redux-toolkit & react. It appears that intercepting a saga call with a token gets redux-saga in an infinite loop? Or is it because of my watchers?
I have recently been learning how to program so my skills in all areas are not yet great, hope you dont mind the way the code is written as I have been following tutorials mostly.
On handleSubmit from a Header.tsx to dispatch
const handleSubmit = (e) => {
e.preventDefault();
dispatch(getCurrentUser());
};
my rootSaga.tsx includes all watcherSagas notices the dispatch for getCurrentUser()
import { takeLatest } from "redux-saga/effects";
import {
handleLogInUser,
handleGetCurrentUser,
handleSetCurrentUser,
} from "./handlers/user";
import {
logInUser,
getCurrentUser,
setCurrentUser,
} from "../slices/user/userSlice";
export function* watcherSaga() {
yield takeLatest(logInUser.type, handleLogInUser);
yield takeLatest(getCurrentUser.type, handleGetCurrentUser);
yield takeLatest(setCurrentUser.type, handleSetCurrentUser);
}
the watcher calls handleGetCurrentUser for the saga located in user.tsx file in handler folder:
import { call, put } from "redux-saga/effects";
import { setCurrentUser } from "../../slices/user/userSlice";
import { requestLogInUser, requestGetCurrentUser } from "../requests/user";
export function* handleLogInUser(action) {
try {
console.log(action + "in handleLogInUser");
yield call(requestLogInUser(action));
} catch (error) {
console.log(error);
}
}
export function* handleGetCurrentUser(action) {
try {
const response = yield call(requestGetCurrentUser);
const userData = response;
yield put(setCurrentUser({ ...userData }));
} catch (error) {
console.log(error);
}
}
Which then uses yield call to requestGetCurrentUser which fires off the request to the following user.tsx in requests folder
import axiosInstance from "../../../axios/Axios";
export function requestGetCurrentUser() {
return axiosInstance.request({ method: "get", url: "/user/currentUser/" });
}
The response is given back and put in const userData, I consoleLog()'d the handler and discovered the following:
it will reach the handler successfully
go to the yield call
obtain the data successfully
return the data back to the handler
then it restarts the entire yield call again?
It also never makes it back to the userSlice in order to put the data.
axiosInstance in my axios.tsx file which includes the interceptor and gets the access_token and adds it to the header.
import axios from "axios";
const baseURL = "http://127.0.0.1:8000/api/";
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
Authorization: "Bearer " + localStorage.getItem("access_token"),
"Content-Type": "application/json",
accept: "application/json",
},
});
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (typeof error.response === "undefined") {
alert(
"A server/network error occurred. " +
"Looks like CORS might be the problem. " +
"Sorry about this - we will get it fixed shortly."
);
return Promise.reject(error);
}
if (
error.response.status === 401 &&
originalRequest.url === baseURL + "token/refresh/"
) {
window.location.href = "/login/";
return Promise.reject(error);
}
if (
error.response.data.code === "token_not_valid" &&
error.response.status === 401 &&
error.response.statusText === "Unauthorized"
) {
const refreshToken = localStorage.getItem("refresh_token");
if (refreshToken) {
const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
console.log(tokenParts.exp);
if (tokenParts.exp > now) {
return axiosInstance
.post("/token/refresh/", {
refresh: refreshToken,
})
.then((response) => {
localStorage.setItem("access_token", response.data.access);
localStorage.setItem("refresh_token", response.data.refresh);
axiosInstance.defaults.headers["Authorization"] =
"JWT " + response.data.access;
originalRequest.headers["Authorization"] =
"JWT " + response.data.access;
return axiosInstance(originalRequest);
})
.catch((err) => {
console.log(err);
});
} else {
console.log("Refresh token is expired", tokenParts.exp, now);
window.location.href = "/login/";
}
} else {
console.log("Refresh token not available.");
window.location.href = "/login/";
}
}
// specific error handling done elsewhere
return Promise.reject(error);
}
);
export default axiosInstance;
The userSlice.tsx
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: {},
reducers: {
logInUser(state, action) {},
getCurrentUser() {},
setCurrentUser(state, action) {
const userData = action.payload;
console.log(userData + "we are now back in slice");
return { ...state, ...userData };
},
},
});
export const { logInUser, getCurrentUser, setCurrentUser } = userSlice.actions;
export default userSlice.reducer;
I discovered that if I were to remove the authorization token it only fires off once and gets out of the infinite loop since it throws the unauthorised error.
Any suggestions would be greatly appreciated, thanks!
Apologies for getting back so late, I managed to fix it a while ago by pure chance and I dont exactly understand why.
But I believe what fixed it were the following two things:
Changing the useEffect that dispatched the action and ensuring that the handler returned data that the useEffect was expecting to be updated.
In the handler I deconstructed the userData to { userData } which I believe means that the data returned from the axios request is not the entire request but the actual returned data.
my handler
export function* handleGetCurrentUser() {
try {
console.log("in request get user");
const response = yield call(requestGetCurrentUser);
const { data } = response;
yield put(setCurrentUser({ ...data }));
} catch (error) {
console.log(error);
}
}
I forgot to add my useEffect to the post, which created the action.
my useEffect in the App.tsx would dispatch the call when the App was rendered for the first time. However because the returned data did not update what was expected it kept rerendering.
I cant exactly remember what my useEffect was but currently it is the following:
my useEffect in App.tsx
const dispatch = useDispatch();
useEffect(() => {
dispatch(getCurrentUser());
}, [dispatch]);
const user = useSelector((state) => state.user);

am trying to redirect the user but the this.$router.push('/') is giving me undefined

am trying to redirect the user to the welcome page when the user logout and tot he home page, when the user logs back in..but the this.$router.push('/'), is giving me undefined.
Here is the code
handleAuthStateChanged: ({ commit }) => {
auth.onAuthStateChanged(user => {
if (user) {
commit("setLogin", true);
console.log("login");
//get current user details
let userId = auth.currentUser.uid;
db.collection("users")
.doc(userId)
.get()
.then(snapshot => {
if (snapshot.exists) {
let currentUser = snapshot.data();
commit("setUser", currentUser);
console.log(currentUser);
} else {
// snapshot.data() will be undefined in this case
console.log("No such document!");
}
});
this.$router.push("/");
} else {
console.log("logout");
commit("setLogin", false);
commit("setUser", null);
this.$router.replace("/welcome");
}
});
}
enter image description here
You are accessing this within the onAuthStateChanged function scope, which means this inside the scope will refers to the own function (since you are using arrow function), not Vue instance.
This wont work:
handleAuthStateChanged: ({ commit }) => {
auth.onAuthStateChanged(user => {
...
// `this` is not a Vue instance
this.$router.push("/");
})
}
You need to make a variable that refer to Vue instance first outside the scope so you can call it inside the function scope, for example:
handleAuthStateChanged: ({ commit }) => {
const self = this;
auth.onAuthStateChanged(user => {
...
// `this` is not a Vue instance, but `self` is
self.$router.push("/");
})
}
Or don't use arrow function since this inside an arrow function refers to the function it self, for example:
handleAuthStateChanged: ({ commit }) => {
auth.onAuthStateChanged(function(user) {
...
// `this` is a Vue instance
this.$router.push("/");
})
}

Resources