I'm trying to implement Vuex in an app I'm building to learn more about Vue.js. The idea is pretty simple: retrieving user information and a list of items (everything is stored on Firebase).
I get the idea of Vuex, but the tutorials I can find only rely on data stored locally in the store. I can't get my head around how it would work when the data in the store has to be kept in sync with an external database.
Did I totally miss something? or maybe is Vuex not the best solution for that?
If you want "the data in the (Vue.js) store to be kept in sync with an external (Firestore) database", you could do as follows, taking advantage of the onSnapshot() method which "attaches a listener for QuerySnapshot events".
Let's imagine you have a cities collection in your Firestore database, and each document of this collection has a field name, which holds the city name.
First, declare the Firebase config in a firebaseConfig.js file:
firebaseConfig.js
import firebase from 'firebase/app';
import 'firebase/firestore';
// firebase init goes here
const config = {
apiKey: 'xxxxxxxxxxxxxxxxxx',
authDomain: 'xxxxxxxxx.firebaseapp.com',
databaseURL: 'xxxxxxxxxxxxxxxxxx',
projectId: 'xxxxxxxxxxxxxxxxxx'
};
firebase.initializeApp(config);
const db = firebase.firestore();
export { db };
Then set-up your Vuex store as follows:
store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
cities: []
},
mutations: {
SET_CITIES(state, val) {
state.cities = val;
}
},
actions: {
//You may add here an action that would commit the SET_CITIES mutation
}
});
Then, modify the main.js file as follows:
main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const fb = require('./firebaseConfig.js');
Vue.config.productionTip = false;
new Vue({
router,
store,
beforeCreate() {
fb.db.collection('cities').onSnapshot(querySnapshot => {
var c = [];
querySnapshot.forEach(doc => {
c.push({
id: doc.id,
name: doc.data().name
});
});
store.commit('SET_CITIES', c);
});
},
render: h => h(App)
}).$mount('#app');
You are all set! Just try getting the cities array in a Component, as follows:
HelloWorld.vue
<template>
<div>
<ul>
<li v-for="c in cities" v-bind:key="c.id">{{ c.name }}</li>
</ul>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "HelloWorld",
computed: {
...mapState(["cities"])
}
};
</script>
and try adding, removing or modifying records in the database.
Related
I followed the example in the documentation under v2.0.0 > Read Me > Load Data (listeners automatically managed on mount/unmount) (direct link is not possible).
And replaced the connect call with the firestore specific one shown here](http://react-redux-firebase.com/docs/firestore.html#examples) in Example 1.
I copied the Todo example exactly in a new component created for testing purposes.
Todo Component:
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { firebaseConnect,firestoreConnect, isLoaded, isEmpty } from 'react-redux-firebase'
const Todos = ({ todos, firebase }) => {
// Build Todos list if todos exist and are loaded
const todosList = !isLoaded(todos)
? 'Loading'
: isEmpty(todos)
? 'Todo list is empty'
: Object.keys(todos).map(
(key, id) => (
<TodoItem key={key} id={id} todo={todos[key]}/>
)
)
return (
<div>
<h1>Todos</h1>
<ul>
{todosList}
</ul>
<input type="text" ref="newTodo" />
<button onClick={this.handleAdd}>
Add
</button>
</div>
)
}
// export default compose(
// firestoreConnect([
// 'todos' // { path: '/todos' } // object notation
// ]),
// connect((state) => ({
// todos: state.firestore.data.todos,
// profile: state.firestore.profile // load profile
// }))
// )(Todos)
export default compose(
firestoreConnect(['todos']), // or { collection: 'todos' }
connect((state, props) => ({
todos: state.firestore.ordered.todos
}))
)(Todos)
The store configuration was configured as shown here in the docs. The store configuration was adapted to slot into the framework created by react-boilerplate.
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux'
import { fromJS } from 'immutable'
import { routerMiddleware } from 'connected-react-router/immutable'
import createSagaMiddleware from 'redux-saga'
import { reactReduxFirebase, firebaseReducer } from 'react-redux-firebase'
import { reduxFirestore, firestoreReducer } from 'redux-firestore'
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
import 'firebase/firestore'
import createReducer from './reducers'
const sagaMiddleware = createSagaMiddleware()
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
}
const rrfConfig = {
userProfile: 'users',
// useFirestoreForProfile: true, // Firestore for Profile instead of Realtime DB
// attachAuthIsReady: true
}
// Initialize Cloud Firestore through Firebase
export default function configureStore(initialState = {}, history) {
firebase.initializeApp(firebaseConfig)
// Initialize Firestore with timeshot settings
firebase.firestore()
// firebase.firestore().settings({ timestampsInSnapshots: true })
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history)]
const enhancers = [
applyMiddleware(...middlewares),
// reactReduxFirebase(config), // enhancing our store with these packages
// reduxFirestore(config)
]
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle, indent */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose
/* eslint-enable */
const createStoreWithFirebase = compose(
reactReduxFirebase(firebase, rrfConfig), // firebase instance as first argument
reduxFirestore(firebase),
)(createStore)
const store = createStoreWithFirebase(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
)
// Extensions
store.runSaga = sagaMiddleware.run
store.injectedReducers = {} // Reducer registry
store.injectedSagas = {} // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers))
})
}
return store
}
I traced and verified my store configuration exactly to make sure all steps present in the documentation are configured correctly in my configuration.
My createReducer funciton is in a seperate file and you can see that I added the firebaseReducer and firebaseReducer correctly.
import { combineReducers } from 'redux-immutable'
import { connectRouter } from 'connected-react-router/immutable'
import { firebaseReducer } from 'react-redux-firebase'
import { firestoreReducer } from 'redux-firestore'
import history from 'utils/history'
import languageProviderReducer from 'containers/LanguageProvider/reducer'
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
firebase: firebaseReducer,
firestore: firestoreReducer,
language: languageProviderReducer,
...injectedReducers,
})
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history)
return mergeWithRouterState(rootReducer)
}
My redux store contains the firestore and firebase and it is injected into the component props.
What does not work is the use of connectFirestore HoC to automatically retrieve and inject a list of documents in to the component.
This is the error message:
react-dom.development.js?61bb:20266 Uncaught TypeError: Cannot read property 'ordered' of undefined
at Function.eval [as mapToProps] (index.js?d834:49)
at mapToPropsProxy (wrapMapToProps.js?1817:54)
at Function.detectFactoryAndVerify (wrapMapToProps.js?1817:63)
at mapToPropsProxy (wrapMapToProps.js?1817:54)
at handleFirstCall (selectorFactory.js?805c:37)
at pureFinalPropsSelector (selectorFactory.js?805c:85)
at Object.runComponentSelector [as run] (connectAdvanced.js?48b8:43)
at Connect.initSelector (connectAdvanced.js?48b8:195)
at new Connect (connectAdvanced.js?48b8:136)
at constructClassInstance (react-dom.development.js?61bb:11315)
(Snipped from my code which is the example 1 in documentation):
export default compose(
firestoreConnect(['todos']), // or { collection: 'todos' }
connect((state, props) => ({
todos: state.firestore.ordered.todos
}))
)(Todos)
I inspected the state variable and it does contain the firestore attribute. This attribute contains a number of functions, as expected, but it is missing the query results under "ordered", which is undefined.
I have tried all different ways to use firestoreconnect e.g. using a Class-based component, using a query with parameters, etc. and all give the same error.
My Firebase project is configured correct as I am able to create documents inside collections. A todos collection for testing purposes is present as well containing 2 documents.
I have come across this post, which mentions the following:
If you just upgraded to React-Redux v6, it's because react-redux-firebase is not compatible with v6.
See https://github.com/prescottprue/react-redux-firebase/issues/581 for details.
This does not apply to me because I am using react-redux version 5. Here are the versions I am using:
"firebase": "^5.10.1",
"react-redux": "^5.0.7",
"react-redux-firebase": "^2.2.6",
"redux": "^4.0.1",
"redux-firestore": "^0.7.3",
I have spent a significant amount of time on this. Like I said, using firestore to add new data to collections works fine. It is just this HoC business that is failing no matter how i approach the solution.
any help would be appreciated.
Never solved this. I guess it is related to incompatible versions. What I ended up doing is download v4 of react-boilerplate and set up v3 react-redux-firebase which uses the Context API as opposed to store enhancers. Now works very well.
I'm building my first Vue.js app, trying to use vuex with vuexfire.
//main.js
import firebase from 'firebase';
...
Vue.prototype.$firebase = firebase.initializeApp(config);
...
firebase.auth().onAuthStateChanged(() => {
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
router,
render: h => h(App),
created() {
this.$store.dispatch('setDealsRef');
},
});
});
router.beforeEach((to, from, next) => {
const currentUser = firebase.auth().currentUser;
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth && !currentUser) {
next('/signin');
} else if (requiresAuth && currentUser) {
next();
} else {
next();
}
});
And:
//store/index.js
import Vue from 'vue';
import Vue from 'vue';
import Vuex from 'vuex';
import { firebaseMutations } from 'vuexfire';
...
Vue.use(Vuex);
const db = this.$firebase.firestore();
const dealsRef = db.collection('deals');
And:
//store/mutations.js
export default {
SET_USER(state) {
state.user = this.$firebase.auth().currentUser;
},
...
}
This complies OK, but throws TypeError: this.$firebase is undefined[Learn More] in the console.
Any idea what I'm doing wrong? I think I've read every relevant tutorial and StackOverflow questions, and tried everything.
When you do:
Vue.prototype.$firebase = firebase.initializeApp(config);
You add $firebase to the Vue instance. So for
this.$firebase
to work, the this should be a Vue insteance. In other words, that line must execute inside a Vue method/hook/computed/etc.
And the code you show, doesn't do that:
const db = this.$firebase.firestore();
in the code above, the this is the outer context. (Probably is window.)
So for it to work outside a Vue instance, you have to do:
const db = Vue.prototype.$firebase.firestore();
Provided the line above executes after (in time/order) the line where you initialize the $firebase.
I think I solved the problem:
Moving firebase initialization to store.js
Changing firebase.auth().onAuthStateChanged(() => { to Vue.prototype.firebase.auth().onAuthStateChanged(() => { in main.js
Importing firebase as: import firebase from '#firebase/app';
import '#firebase/firestore';
I was using Vue Resource in posting data from my web app to the firebase. but then, I just found out that I need to use firebase integration to upload IMAGES in the firebase storage. so I integrated it in my src/main.js
import Vue from 'vue'
import VueResource from 'vue-resource'
import VueRouter from 'vue-router'
import * as firebase from 'firebase'
import App from './App.vue'
import Routes from './routes'
Vue.use(VueResource);
Vue.use(VueRouter);
const router = new VueRouter({
routes: Routes,
mode: 'history'
});
new Vue({
el: '#app',
render: h => h(App),
router: router,
created () {
firebase.initializeApp({
apiKey: 'AIzaSyDhdEhcLPfGqo5_msnhVKWH9BkZNOc6RYw',
authDomain: 'nots-76611.firebaseapp.com',
databaseURL: 'https://nots-76611.firebaseio.com',
projectId: 'nots-76611',
storageBucket: 'gs://nots-76611.appspot.com'
})
}
})
but when I tried to use it in one of my components' methods:
methods: {
post: function(){
//for(var i = 0; i < this.tailors.length; i++){
// if(this.$route.params.id == this.tailors[i].id)
// this.ready_to_wear.tailor_name = this.tailors[i].tName;
//}
//this.$http.post('https://nots-76611.firebaseio.com/ready_to_wear.json', this.ready_to_wear);
let key
firebase.database().ref('ready_to_wears').push(this.ready_to_wear)
.then((data) => {
key = data.key
return key
})
.then(key => {
const filename = this.image.name
const ext = filename.slice(filename.lastIndexOf('.'))
return firebase.storage().ref('rtws/' + key + '.' + ext).put(this.image)
})
.then(fileData => {
imageUrl = fileData.metadata.downloadURLs[0]
return firebase.database().ref('ready_to_wears').child(key).update({rtwImg: imageUrl})
});
}
}
.. it says in the console log that 'firebase' is not defined
I'm guessing that firebase functions can't be used in the components even though it is integrated in the main.js
How do I make use of it in the components? is there any other way around it?
You don't appear to be using VueFire, which I believe exposes firebase through a Vue prototype property as $firebase. However, you can do it yourself manually.
import Vue from 'vue'
import VueResource from 'vue-resource'
import VueRouter from 'vue-router'
import * as firebase from 'firebase'
import App from './App.vue'
import Routes from './routes'
Vue.prototype.$firebase = firebase
After that, firebase will be available in every Vue or component as this.$firebase.
methods: {
post: function(){
this.$firebase.database().ref() ... etc ...
}
}
I'm developing a React-native app, using Redux and Firebase.
My Firebase database is denormalized, so it looks like:
users:
user_uid:
my_posts: [ post_key1, post_key2 ]
posts
post_key1: { post_details }
post_key2: { post_details }
How should I fetch data asynchronously and dispatch posts data to Redux store?
I know about Firebase methods .on('value') and .once('value'), but I'm not able to write a proper async function/thunk without generating issues.
If you are using react-redux-firebase to integrate redux with Firebase, the v2.0.0 docs show using react-native with examples for using either native-modules through react-native-firebase or the JS SDK.
With the structure you have shown, it may also be helpful for you to use populate to easily load posts automatically when loading users.
If you have the users uid on the post object under owner, you could do something like:
Home.js
import { compose } from 'redux'
import { connect } from 'react-redux'
import { firebaseConnect, populate } from 'react-redux-firebase'
const populates = [
{ child: 'owner', root: 'users' } // replace owner with user object
]
const enhance = compose(
firebaseConnect([
// passing populates parameter also creates all necessary child queries
{ path: 'posts', populates }
]),
connect(({ firebase }) => ({
// populate original from data within separate paths redux
posts: populate(firebase, 'posts', populates),
}))
)
const SomeComponent = ({ posts }) => <div>{JSON.stringify(posts, null, 2)}</div>
export default enhance(SomeComponent)
App.js
import { createStore, combineReducers, compose } from 'redux'
import { connect } from 'react-redux'
import { reactReduxFirebase, firebaseReducer } from 'react-redux-firebase'
import firebase from 'firebase'
import Home from './Home' // code above
const firebaseConfig = {} // config from firebase console
// react-redux-firebase config
const rrfConfig = {
userProfile: 'users' // automatically manage profile
}
// initialize firebase instance
firebase.initializeApp(config) // <- new to v2.*.*
// Add reduxReduxFirebase enhancer when making store creator
const createStoreWithFirebase = compose(
reactReduxFirebase(firebase, rrfConfig)
)(createStore)
// Add Firebase to reducers
const rootReducer = combineReducers({
firebase: firebaseStateReducer
})
// Create store with reducers and initial state
const initialState = {}
const store = createStoreWithFirebase(rootReducer, initialState)
const App = () => (
<Provider store={store}>
<Home />
</Provider>
);
ReactDOM.render(<App/>, document.querySelector('#app'));
I'm trying to set up a vue-fire app using single file Vue components.
I'm using the standard (full) Vue-cli Webpack template available on the official site.
I have firebase loaded in App.vue like this:
let config = {
...
};
let app = Firebase.initializeApp(config);
let db = app.database();
let usersRef = db.ref('users');
...
export default {
name: 'app',
data () {
return {
login: {
email: '',
password: ''
},
newUser: {
email: '',
password: ''
},
showRegister: false
}
},
firebase: {
users: usersRef,
},
...
}
I'm using Vue-router and my routes are set up like this:
import Vue from 'vue'
import Router from 'vue-router'
import Home from '#/components/Home'
import News from '#/components/News'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/news',
name: 'News',
component: News
}
]
})
I would like to be able to access my Firebase app in the 'News' component. The problem is that if I include the entire Firbase setup in the News.vue file, I get the error:
[DEFAULT]: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
The recommended solution is to export the initialized app's database in App.vue and import it in the child component. So I add this to the bottom of my App.vue script:
module.exports.FBApp = app.database();
And this to News.vue:
import FBApp from '../App.vue'
let usersRef = FBApp.ref('users')
But now I am getting the following error:
TypeError: __WEBPACK_IMPORTED_MODULE_0__App_vue___default.a.ref is not a function
Does anyone know how to do this? Surely it can't be too hard.
Create a db.js file like the following alongside app.vue.
import firebase from 'firebase'
var config = {
apiKey: 'xxxxx'
authDomain: 'xxxxx'
databaseURL: 'xxxxx'
projectId: 'xxxxx'
storageBucket: 'xxxxx'
messagingSenderId: 'xxxxx'
}
const firebaseApp = firebase.initializeApp(config)
const db = firebaseApp.database()
export default db
In your main.js:
import Vue from 'vue'
import App from './App'
import router from './router'
import VueFire from 'vuefire'
// explicit installation required in module environments
Vue.use(VueFire)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
And now in any component, eg:
<template>
<span>
{{ news }}
</span>
</template>
<script>
import db from '../db'
export default {
data: function () {
return {
users: [],
sample: []
}
},
firebase: function () {
return {
news: db.ref('news')
}
}
}
</script>