-
-
Save markopavlovic/b71f098e27e2057aa418bcc64025e2e6 to your computer and use it in GitHub Desktop.
/** | |
* @path src/redux/middleware | |
* | |
* Extract from clientMiddleware example in order to showcase | |
* the difference and help in understanding the same. | |
* | |
* @diff - Differences with "react-redux-universal-hot-example" | |
*/ | |
export default function firebaseMiddleware(fireRef) { | |
return ({dispatch, getState}) => { | |
return next => action => { | |
if (typeof action === 'function') { | |
return action(dispatch, getState); | |
} | |
// @diff Look for the firebase related actions instead of promise/client ones | |
const { firebase, types, ...rest } = action; // eslint-disable-line no-redeclare | |
// @diff if there is none, move on, same as promise/client | |
if (!firebase) { | |
return next(action); | |
} | |
const [REQUEST, SUCCESS, FAILURE] = types; | |
next({...rest, type: REQUEST}); | |
const firebasePromise = firebase(fireRef); | |
firebasePromise.then( | |
(result) => next({...rest, result, type: SUCCESS}), | |
(error) => next({...rest, error, type: FAILURE}) | |
).catch((error)=> { | |
console.error('MIDDLEWARE ERROR:', error); | |
next({...rest, error, type: FAILURE}); | |
}); | |
return firebasePromise; | |
}; | |
}; | |
} |
/** | |
* @path src/redux | |
* | |
* Simple store binding change | |
* | |
* @diff - Differences with "react-redux-universal-hot-example" | |
*/ | |
import { createStore as _createStore, applyMiddleware, compose } from 'redux'; | |
import createMiddleware from './middleware/clientMiddleware'; | |
// @diff firebaseMiddleware | |
import firebaseMiddleware from './middleware/firebaseMiddleware'; | |
import { syncHistory } from 'react-router-redux'; | |
// @diff accept firebase reference | |
export default function createStore(history, {client, firebase}, data) { | |
// Sync dispatched route actions to the history | |
const reduxRouterMiddleware = syncHistory(history); | |
// @diff line up firebase middleware | |
const middleware = [createMiddleware(client), firebaseMiddleware(firebase), reduxRouterMiddleware]; | |
let finalCreateStore; | |
if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) { | |
const { persistState } = require('redux-devtools'); | |
const DevTools = require('../containers/DevTools/DevTools'); | |
finalCreateStore = compose( | |
applyMiddleware(...middleware), | |
window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument(), | |
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)) | |
)(_createStore); | |
} else { | |
finalCreateStore = applyMiddleware(...middleware)(_createStore); | |
} | |
const reducer = require('./modules/reducer'); | |
const store = finalCreateStore(reducer, data); | |
reduxRouterMiddleware.listenForReplays(store); | |
if (__DEVELOPMENT__ && module.hot) { | |
module.hot.accept('./modules/reducer', () => { | |
store.replaceReducer(require('./modules/reducer')); | |
}); | |
} | |
return store; | |
} |
/** | |
* @path src | |
* | |
* @diff - Differences with "react-redux-universal-hot-example" | |
*/ | |
import 'babel-polyfill'; | |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import createStore from './redux/create'; | |
import ApiClient from './helpers/ApiClient'; | |
// @diff include Firebase, this is my helper, you can just use firebase package | |
import Firebase from './helpers/Firebase.js'; | |
import io from 'socket.io-client'; | |
import {Provider} from 'react-redux'; | |
import { Router, browserHistory } from 'react-router'; | |
import { ReduxAsyncConnect } from 'redux-async-connect'; | |
import useScroll from 'scroll-behavior/lib/useStandardScroll'; | |
import getRoutes from './routes'; | |
// @diff fire it up | |
const firebase = new Firebase(); | |
const client = new ApiClient(); | |
const history = useScroll(() => browserHistory)(); | |
const dest = document.getElementById('content'); | |
// @diff param | |
const store = createStore(history, {client, firebase}, window.__data); | |
// ... |
/** | |
* @path src | |
* | |
* Make the server.js adopt to the store changes. | |
* Replace. | |
*/ | |
const store = createStore(history, {client}); | |
//const store = createStore(history, client); |
/** | |
* @path src/redux/modules | |
* | |
* Finally lets change old action using the client API | |
* into a Firebase login. | |
* Be sure to pass over a new 'password' param from containers/Login. | |
* | |
* Replace. | |
*/ | |
export function login(name, password) { | |
return { | |
types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL], | |
firebase: (fireRef) => fireRef.authWithPassword({password: password, email: name}) | |
} | |
} | |
/** | |
-- OLD -- | |
export function login(name) { | |
return { | |
types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL], | |
promise: (client) => client.post('/login', { | |
data: { | |
name: name | |
} | |
}) | |
}; | |
} | |
*/ |
@markopavlovic Thanks for the great resource. It helped me a lot. An issue I'm facing right now, is to listen for changes within the Firebase database. The firebaseMiddleware()
is expecting a promise, which means that I need to use once()
instead of on()
from the Firebase API.
In my case I want to listen for changes and then update the Redux state.
The once()
method is working.
export function loadProducts() {
return {
types: [LOAD_PRODUCTS, LOAD_PRODUCTS_SUCCESS, LOAD_PRODUCTS_FAIL],
firebase: (fireRef) => fireRef.child('products').once('value', (snap) => snap),
};
}
But I want this to use the on()
method instead.
export function loadProducts() {
return {
types: [LOAD_PRODUCTS, LOAD_PRODUCTS_SUCCESS, LOAD_PRODUCTS_FAIL],
firebase: (fireRef) => fireRef.child('products').on('value', (snap) => snap),
};
}
Can you help me with this issue?
This is absolutely stellar. Thank you!
@markhaasjes First of all, thank you and sorry for a very late reply.
I am not quite sure how your app logic should be done, but I would use .once() for the initial load and then set .on('child_added')
at the component level and update the props through reducer. So lets imagine we already have loadProducts()
with .once()
set in place for the initial render and then in component level we would go for something like this.
Component (listener):
componentDidMount() {
// var fireRef = '...';
// var updateProducts = actions.updateProducts;
// You can limit the child_added to return just new results
fireRef.child('products').on('child_added', (snap) => this.props.updateProducts(snap.val()));
}
Reducer:
export default function reducer(state = initialState, action = {}) {
case NEW_PRODUCTS:
const {products} = state;
return {
...state,
products: products.push(action.newProducts)
};
}
export function updateProducts(products) {
return {
types: [NEW_PRODUCTS],
newProducts: products
};
}
Further on, how you will handle the update of products is up to you. Hope this helps :)
Hello thanks for this, is wath i need !
but i have question, i try to use your code but with an action creator like this one :
this actually work only on client side
export function fetchTodos() { console.log('fetchTodos') return dispatch => { firebase.database().ref("todo").on('value', dataSnapshot => { var data = []; dataSnapshot.forEach(function (item) { let todo = item.val(); todo.id = item.key; data.push(todo); }); dispatch({ type: actionsTypes.TODO_FETCH_SUCCESS, todos: data }); }); } }
https://gist.github.com/madbean/598b766121cff523f731d976445db329
but that give an error i dont understand : The XMLHttpRequest compatibility library was not found.
i m using firebase 3.0
another question in the server.js file we don't pass firebase to the store ?
like this : const store = createStore(history, {client, firebase});
thank for your time !
Great resource! Thanks!