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

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>

Related

How to get stripe customers in next js

I am using Stripe in my NextJs project and have tried to get customers list in my app but have not succeeded. If anyone knows how to get it, please instruct me on how to do that.
This is my code:
import { loadStripe } from "#stripe/stripe-js";
async function getStripeCustomers(){
const stripe = await loadStripe(
process.env.key
);
if (stripe) {
// there was a toturail for node.js like this.
console.log(stripe.customers.list())
}
}
useEffect(() => {
getStripeCustomers()
}, []);
I think you should do this logic in backend so create a route in api folder then try this code.
// api/payment/get-all-customers.js
import Stripe from "stripe";
export default async function handler(req, res) {
if (req.method === "POST") {
const { token } = JSON.parse(req.body);
if (!token) {
return res.status(403).json({ msg: "Forbidden" });
}
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET, {
apiVersion: "2020-08-27",
});
try {
const customers = await stripe.customers.list(); // returns all customers sorted by createdDate
res.status(200).json(customers);
} catch (err) {
console.log(err);
res.status(500).json({ error: true });
}
}
}
Now from frontend send a POST request to newly created route.

Nuxt async not work on page reload - 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

Protected Route With Firebase and Svelte

I'm trying to create a protected page, the profile page of my project. I want to throw people out if they are not logged in. I'm trying to do it as simply as possible. I find this tutorial, but is TypeScript and I couldn't get it to work. Link >
My way:
Profile page:
let auth = getAuth();
onMount(() => {
auth.onAuthStateChanged((user) => {
if (!user) {
goto('/signin');
}
});
});
The idea is to have a user store and use it with the combination of onAuthStateChanged
import authStore from '../stores/authStore';; // <~ stores.ts
import { onMount } from 'svelte';
let auth = getAuth();
onMount(() => {
//shift this method to a firebase.ts
auth.onAuthStateChanged((user) => {
if (user) {
authStore.set({
user,
});
} else {
authStore.set({
user: null,
});
}
});
});
// this block will watch changes on the store
$: {
if (!$authStore.user) {
if (browser) goto('/login');
} else {
if (browser) goto('/');
}
}

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("/");
})
}

Redux-saga firebase onAuthStateChanged eventChannel

How to handle firebase auth state observer in redux saga?
firebase.auth().onAuthStateChanged((user) => {
});
I want to run APP_START saga when my app starts which will run firebase.auth().onAuthStateChanged observer and will run other sagas depending on the callback.
As I understand eventChannel is right way to do it. But I don't understand how to make it work with firebase.auth().onAuthStateChanged.
Can someone show how to put firebase.auth().onAuthStateChanged in to eventChannel?
You can use eventChannel. Here is an example code:
function getAuthChannel() {
if (!this.authChannel) {
this.authChannel = eventChannel(emit => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => emit({ user }));
return unsubscribe;
});
}
return this.authChannel;
}
function* watchForFirebaseAuth() {
...
// This is where you wait for a callback from firebase
const channel = yield call(getAuthChannel);
const result = yield take(channel);
// result is what you pass to the emit function. In this case, it's an object like { user: { name: 'xyz' } }
...
}
When you are done, you can close the channel using this.authChannel.close().
Create your own function onAuthStateChanged() that will return a Promise
function onAuthStateChanged() {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
resolve(user);
} else {
reject(new Error('Ops!'));
}
});
});
}
Then use call method to get the user synchronously
const user = yield call(onAuthStateChanged);
This could be handled in the Saga such as the following for Redux Saga Firebase:
// Redux Saga: Firebase Auth Channel
export function* firebaseAuthChannelSaga() {
try {
// Auth Channel (Events Emit On Login And Logout)
const authChannel = yield call(reduxSagaFirebase.auth.channel);
while (true) {
const { user } = yield take(authChannel);
// Check If User Exists
if (user) {
// Redux: Login Success
yield put(loginSuccess(user));
}
else {
// Redux: Logout Success
yield put(logoutSuccess());
}
}
}
catch (error) {
console.log(error);
}
};
here is how you would run the onAuthStateChanged observable using redux-saga features (mainly eventChannel)
import { eventChannel } from "redux-saga";
import { take, call } from "redux-saga/effects";
const authStateChannel = function () {
return eventChannel((emit) => {
const unsubscribe = firebase.auth().onAuthStateChanged(
(doc) => emit({ doc }),
(error) => emit({ error })
);
return unsubscribe;
});
};
export const onAuthStateChanged = function* () {
const channel = yield call(authStateChannel);
while (true) {
const { doc, error } = yield take(channel);
if (error) {
// handle error
} else {
if (doc) {
// user has signed in, use `doc.toJSON()` to check
} else {
// user has signed out
}
}
}
};
please note that other solutions that don't utilize channel sagas are not optimal for redux-saga, because turning an observable into a promise is not a valid solution in this case since you would need to call the promise each time you anticipate a change in authentication state (like for example: taking every USER_SIGNED_IN action and calling the "promisified" observable), which will negate the whole purpose of an observable

Resources