Skip to content

Instantly share code, notes, and snippets.

@markopavlovic
Last active March 1, 2018 09:23
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save markopavlovic/b71f098e27e2057aa418bcc64025e2e6 to your computer and use it in GitHub Desktop.
Save markopavlovic/b71f098e27e2057aa418bcc64025e2e6 to your computer and use it in GitHub Desktop.
Firebase Middleware on Redux - Follow up on "react-redux-universal-hot-example" (Firebase login example)
/**
* @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
}
})
};
}
*/
@juanbiltes
Copy link

Great resource! Thanks!

@markhaasjes
Copy link

@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?

Copy link

ghost commented Jun 24, 2016

This is absolutely stellar. Thank you!

@markopavlovic
Copy link
Author

markopavlovic commented Aug 11, 2016

@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 :)

@madbean
Copy link

madbean commented Sep 23, 2016

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 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment