vuexfire firestoreAction, binding with arg - firebase

I'm trying to bind my module's store to a document
import Vue from 'vue'
import { db } from '../my-firebase/db'
import { firestoreAction } from 'vuexfire'
export const user = {
...
actions: {
logOutUser: ({ commit }) => {
commit('logOutUser')
},
logInUser: ({ dispatch, commit }, userInfo) => {
let dbRef = db.collection('users').doc(userInfo.uid)
dbRef.update({ authInfo: userInfo })
.then(() => {
commit('logInUser', userInfo)
})
dispatch('bindFirebaseUser', dbRef)
},
bindFirebaseUser: (context, userRef) => {
console.log('Running dispatch BindFirebaseUser')
return firestoreAction(({ bindFirestoreRef }) => {
// return the promise returned by `bindFirestoreRef`
console.log('userRef:')
console.log(userRef)
return bindFirestoreRef('firebaseData', userRef)
})
}
}
}
It's not working. How do I bindFirestoreRef with the argument userRef? It doesn't seem to bind, though I can write to the firestore properly, so I would assume that my db is set up correctly.
It just doesn't give any form of error whatsoever, but if it binds, it should populate my store with the data I wrong shouldn't it?

You can pass the reference as the second argument to firestoreAction
bindFirebaseUser: firestoreAction(({ bindFirestoreRef }, userRef) => {
return bindFirestoreRef('firebaseData', userRef)
})

Related

React component does not update after Redux store changes

I am trying to fetch some data in a react component using the useEffect hook. After the initial render, fetchItems() gets the items and updates the store. However, items is still an empty object even after the store updates.
I might be using useEffects wrong. How do you use Redux with useEffects? I want to set a loading state for the component, but since the component only dispatches an action to fetch items (instead of directly calling the API), it does not know when the data is fetched and the store is updated so it can pull it.
Can someone please help figure out how to make sure that items object is updated after the saga fetch and the subsequent store update?
import React, { useState, useEffect } from "react";
import { connect } from 'react-redux';
import { useParams } from "react-router-dom";
const ItemComponent = ({ item, fetchItem }) => {
const { itemId } = useParams();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true)
fetchItem(itemId)
setIsLoading(false)
}, []);
console.log(item) // gives empty object even after the fetch and store update
}
const mapStateToProps = (state) => {
return {
item: state.item
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItem: (itemId) => { dispatch(fetchItemActionCreator(itemId)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemComponent);
fetchItemActionCreator is an action creator that creates the action to be dispatched.
My reducer and saga work fine as I can see the store actions and updates in the console.
If I pass the items object into the dependency array for useEffect, then there will be an infinite loop and the page keeps re-rendering.
Reducer:
const itemReducer = (state={}, { type, payload }) => {
switch(type) {
case ITEM_GET_SUCCESS:
return {...state, ...payload}
default: return state
}
}
fetchItemActionCreator:
import { createAction } from '#reduxjs/toolkit';
export const fetchItemActionCreator = createAction(ITEM_GET_PENDING);
Thank you very much in advance!
I want to set a loading state for the component
/** Action */
const getItem = () => dispatch => {
dispatch({ type: 'GET_ITEM_START' });
axios
.get('your api end point')
.then(res => {
const item = res.data;
dispatch({
type: 'GET_ITEM_SUCCESS',
payload: {
item,
},
});
})
.catch(error => {
dispatch({
type: 'GET_ITEM_FAIL',
payload: error,
});
});
};
/** Reducer */
const INITIAL_STATE = {
item: null,
error: '',
loading: false,
};
const itemReducer = (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case 'GET_ITEM_START':
return { ...state, error: '', loading: true };
case 'GET_ITEM_SUCCESS':
return { ...state, ...payload, loading: false };
case 'GET_ITEM_FAIL':
return { ...state, error: payload, loading: false };
default:
return state;
}
};
Then your could handle Loading state in your component
const ItemComponent = ({ fetchItem, item, loading, error }) => {
/** ... */
/**
Check for loading and show a spinner or anything like that
*/
useEffect(() => {
fetchItem(itemId);
}, []);
if (loading) return <ActivityIndicator />;
if (item) return <View>{/* renderItem */}</View>;
return null;
};

useEffect infinite loop with dependency array on redux dispatch

Running into an infinite loop when I try to dispatch an action which grabs all recent posts from state.
I have tried the following in useEffect dependency array
Object.values(statePosts)
useDeepCompare(statePosts)
passing dispatch
omitting dispatch
omitting statePosts
passing statePosts
doing the same thing in useCallback
a lot of the suggestions came from here
I have verified that data correctly updates in my redux store.
I have no idea why this is still happening
my component
const dispatch = useDispatch()
const { user } = useSelector((state) => state.user)
const { logs: statePosts } = useSelector((state) => state.actionPosts)
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
actionPosts createSlice
const slice = createSlice({
name: 'actionPosts',
initialState: {
posts: [],
},
reducers: {
postsLoading: (state, { payload }) => {
if (state.loading === 'idle') {
state.loading = 'pending'
}
},
postsReceived: (state, { payload }) => {
state.posts = payload
},
},
})
export default slice.reducer
const { postsReceived, postsLoading } = slice.actions
export const getActionPostsRest = (email) => async (dispatch) => {
try {
dispatch(postsLoading())
const { data } = await getUserActionPostsByUser({ email })
dispatch(postsReceived(data.userActionPostsByUser))
return data.userActionPostsByUser
} catch (error) {
throw new Error(error.message)
}
}
Remove dispatch from dependencies.
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
you cannot use hook as dependency and by the way, ref.current, is always undefined here
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
because useDeepCompare essentially is just a function that you initiate (together with ref) on each call, all it does is just returns value. That's where the loop starts.

Unsubscribe Firestore listener on logout

The straightforward way to do this is explained here
However i am having a hard time trying to trigger the unsubscribe within a onAuthStateChanged which is in a different vuex module
store/user.js
...
onAuthStateChanged({ commit, dispatch }, { authUser }) {
if (!authUser) {
commit('RESET_STORE')
this.$router.push('/')
return
}
commit('SET_AUTH_USER', { authUser })
dispatch('database/getUserItems', null, { root: true })
this.$router.push('/home')
}
...
store/database.js
...
getUserItems({ state, commit }, payload) {
const unsubscribe = this.$fireStore
.collection('useritems')
.where('owner', '==', this.state.user.authUser.uid)
.onSnapshot(
(querySnapshot) => {
querySnapshot.forEach(function(doc) {
// STUFF
},
(error) => {
console.log(error)
}
)
},
...
How do i reference unsubscribe() from the user.js module when the user logs out (authUser undefined)?
I think you can just save it in you Vuex state tree and call it from there.
state: {
//....
listenerUnsubscribe: null,
//....
},
mutations: {
//....
SET_LISTENER_UNSUBSCRIBE(state, val) {
state.listenerUnsubscribe = val;
},
RESET_STORE(state) {
state.listenerUnsubscribe()
}
//....
},
actions: {
//....
getUserItems({ state, commit }, payload) {
const unsubscribe = this.$fireStore
.collection('useritems')
.where('owner', '==', this.state.user.authUser.uid)
.onSnapshot((querySnapshot) => {
querySnapshot.forEach(function(doc) {
// STUFF
},
(error) => {
console.log(error)
}
);
commit('SET_LISTENER_UNSUBSCRIBE', unsubscribe);
},

Firebase trying to return onAuthStateChanged

Trying to implement firebase authentication, the login, sign in, logout are working so trying to return if user is logged in or not.
The userID from my store is returning as undefined/not updating when trying to display it.
When checking in vue dev tools the userId never changes.
It is not logging as an error in my console just as undefined. in a single line with a grey textcolor
<template>
<div>
<button #click="log"></button>
</div>
</template>
<script>
import firebase from 'firebase'
import { mapState } from 'vuex'
export default {
name: 'Navigation',
data () {
return {
}
},
computed: {
...mapState({
userId: state => state.userId
})
},
methods: {
loggedOut() {
this.$store.dispatch('logout')
},
log() {
console.log(this.$store.userId)
}
}
}
</script>
^this is my vue file
import Vue from 'vue'
import Vuex from 'vuex'
import db from '../firebase'
import router from '../router';
// import firebase from '../firebase'
import firebase from 'firebase'
//https://github.com/robinvdvleuten/vuex-persistedstate
Vue.use(Vuex)
import createPersistedState from "vuex-persistedstate"
export const store = new Vuex.Store({
plugins: [createPersistedState()],
state: {
userId: ''
},
mutations: {
LOGOUT(state, userId) {
state.userId = '';
},
SET_USER_ID(state, userId) {
state.userId = userId;
}
},
actions: {
checkUserStatus({ commit, state }) {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
commit('SET_USER_ID', user.uid);
resolve(user.uid);
} else {
reject('User is not logged in.');
}
})
})
},
signIn(context, credentials) {
firebase.auth().createUserWithEmailAndPassword(credentials.username, credentials.password)
.then(data => {
router.push('/')
alert('Signed in!')
})
.catch(e => {
alert(e.message)
})
},
login(context, credentials) {
firebase.auth().signInWithEmailAndPassword(credentials.username, credentials.password)
.then(data => {
router.push('/')
alert('Logged inn')
})
.catch(e => {
console.log(e, ' Loggin failed')
alert(e.message)
})
},
logout(context) {
firebase.auth().signOut()
.then(data => {
router.push('/Login')
alert('logged out')
})
.catch(e => {
alert(e.message)
})
},
...
}
})
export default store

Redux observable cancel next operator execution?

I am using redux-observable with redux for async actions. Inside epic's map operator i am doing some pre processing because its the central place.
My app calling same action from multiple container components with different values.
So basically i have to cancel my ajax request/next operator execution if deepEqual(oldAtts, newAtts) is true
code -
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(function(action) {
let oldAtts = store.getState().catalog.filterAtts
let newAtts = Object.assign({}, oldAtts, action.atts)
if (deepEqual(oldAtts, newAtts)) {
// Don't do new ajax request
}
const searchString = queryString.stringify(newAtts, {
arrayFormat: 'bracket'
})
// Push new state
pushState(newAtts)
// Return new `action` object with new key `searchString` to call API
return Object.assign({}, action, {
searchString
})
})
.mergeMap(action =>
ajax.get(`/products?${action.searchString}`)
.map(response => doFetchProductsFulfilled(response))
.catch(error => Observable.of({
type: FETCH_PRODUCTS_FAILURE,
payload: error.xhr.response,
error: true
}))
.takeUntil(action$.ofType(FETCH_PRODUCTS_CANCEL))
);
}
Not sure whether its right way to do it from epic.
Thanks in advance.
You can do this:
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(action => ({
oldAtts: store.getState().catalog.filterAtts,
newAtts: Object.assign({}, oldAtts, action.atts)
}))
.filter(({ oldAtts, newAtts }) => !deepEqual(oldAtts, newAtts))
.do(({ newAtts }) => pushState(newAtts))
.map(({ newAtts }) => queryString.stringify(newAtts, {
arrayFormat: 'bracket'
}))
.mergeMap(searchString => ...);
}
But most likely you do not need to save the atts to state to do the comparison:
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(action => action.atts)
.distinctUntilChanged(deepEqual)
.map(atts => queryString.stringify(atts, { arrayFormat: 'bracket' }))
.mergeMap(searchString => ...);
}

Resources