React-redux-universal-hot-example: Where would be the best way to load data from Firebase?

Created on 29 Sep 2015  路  8Comments  路  Source: erikras/react-redux-universal-hot-example

I am trying to fetch some data from Firebase.

Where should be the reference to Firebase
_var Firebase = require('firebase/lib/firebase-web')_
_const ref = new Firebase("https://some-app.firebaseio.com")_
and where should be the data retrieved?

Thanks for any help!

Most helpful comment

This is just one way of doing it:

I store the Firebase ref in the Redux store. I then attach a value listener that continually mirrors the Firebase data into a redux reducer. In the actions, I use redux-thunk's getState to access the ref and do any modifications to the data.

I like this way of doing it because:

  • You don't use globals to access the Firebase ref -- It didn't feel right having a piece of state that isn't in the redux store, and it especially feels bad if that state lived as a global in the actions file and could not be easily shared across files.
  • The ref is always inspectable when debugging inside the reducer
  • You can replace the ref with a different one in your test code
  • Multiple reducers can derive their information from a single ref -- e.g. You may have a chatRooms base ref, each chat room might need to append a suffix on the base ref when listening for messages in the room.

An example:

// actions/config.js

/**
 * Called every time the firebase ref changes
 */
function replaceConfig(config) {
  return {
    type: 'CONFIG_REPLACE',
    value: config
  };
}

/**
 * Start listening to changes when the app boots
 */
function listenToConfigChanges() {
  return (dispatch, getState) => {
    const { firebaseRef } = getState();
    firebaseRef.child('config').on('value', (snapshot) => {
      dispatch(replaceConfig(snapshot.val()));
    });
  };
}

/*
 * Save new config data
 */
function saveConfig(config) {
  return (dispatch, getState) => {
    const { firebaseRef } = getState();
    firebaseRef.child('config').set(config);
    // no need for dispatch, it will trigger Firebase 'value', which will dispatch `replaceConfig`
  };
}
// reducers/config.js
const initialState = {};

function config(state = initialState, action) {
  switch (action.type) {
  case 'CONFIG_REPLACE':
    return Object.assign({}, action.value); // note: we replace state entirely here
  default:
    return state;
  }
}
// actions/firebaseRef.js

function setFirebaseRef(ref) {
  return {
    type: 'FIREBASE_REF_SET',
    value: ref
  };
}
// reducers/firebaseRef.js
const initialState = null;

function firebaseRef(state = initialState, action) {
  switch (action.type) {
  case 'FIREBASE_REF_SET':
    return action.value;
  default:
    return state;
  }
}

Then, on app boot, set the Firebase ref and start listening to changes:

var config = {}; // this will come from your environment specific config
var ref = new Firebase(config.firebaseRef);

var actions = bindActionCreators(/* ... */);
actions.setFirebaseRef(ref);

An on the config panel:

// start listening somewhere
this.props.actions.listenToConfigChanges();

// to save some data
this.props.actions.saveConfig({
  new: 'config'
});

Note: I used on('value') listeners in this example for brevity. You would modify the example to use the child_added/child_changed/child_removed events if you were dealing with a collection of things.

All 8 comments

This is just one way of doing it:

I store the Firebase ref in the Redux store. I then attach a value listener that continually mirrors the Firebase data into a redux reducer. In the actions, I use redux-thunk's getState to access the ref and do any modifications to the data.

I like this way of doing it because:

  • You don't use globals to access the Firebase ref -- It didn't feel right having a piece of state that isn't in the redux store, and it especially feels bad if that state lived as a global in the actions file and could not be easily shared across files.
  • The ref is always inspectable when debugging inside the reducer
  • You can replace the ref with a different one in your test code
  • Multiple reducers can derive their information from a single ref -- e.g. You may have a chatRooms base ref, each chat room might need to append a suffix on the base ref when listening for messages in the room.

An example:

// actions/config.js

/**
 * Called every time the firebase ref changes
 */
function replaceConfig(config) {
  return {
    type: 'CONFIG_REPLACE',
    value: config
  };
}

/**
 * Start listening to changes when the app boots
 */
function listenToConfigChanges() {
  return (dispatch, getState) => {
    const { firebaseRef } = getState();
    firebaseRef.child('config').on('value', (snapshot) => {
      dispatch(replaceConfig(snapshot.val()));
    });
  };
}

/*
 * Save new config data
 */
function saveConfig(config) {
  return (dispatch, getState) => {
    const { firebaseRef } = getState();
    firebaseRef.child('config').set(config);
    // no need for dispatch, it will trigger Firebase 'value', which will dispatch `replaceConfig`
  };
}
// reducers/config.js
const initialState = {};

function config(state = initialState, action) {
  switch (action.type) {
  case 'CONFIG_REPLACE':
    return Object.assign({}, action.value); // note: we replace state entirely here
  default:
    return state;
  }
}
// actions/firebaseRef.js

function setFirebaseRef(ref) {
  return {
    type: 'FIREBASE_REF_SET',
    value: ref
  };
}
// reducers/firebaseRef.js
const initialState = null;

function firebaseRef(state = initialState, action) {
  switch (action.type) {
  case 'FIREBASE_REF_SET':
    return action.value;
  default:
    return state;
  }
}

Then, on app boot, set the Firebase ref and start listening to changes:

var config = {}; // this will come from your environment specific config
var ref = new Firebase(config.firebaseRef);

var actions = bindActionCreators(/* ... */);
actions.setFirebaseRef(ref);

An on the config panel:

// start listening somewhere
this.props.actions.listenToConfigChanges();

// to save some data
this.props.actions.saveConfig({
  new: 'config'
});

Note: I used on('value') listeners in this example for brevity. You would modify the example to use the child_added/child_changed/child_removed events if you were dealing with a collection of things.

Thanks for fielding this so beautifully, @tstirrat.

Absolutely @erikras :+1: Many thanks for your comprehensive answer @tstirrat I will dig into that. So that I won't need the projects api in this case?

I added the

actions.setFirebaseRef(ref);

to

componentWillMount() 

to App.js container, but I'm getting the "ROUTER ERROR: TypeError: Converting circular structure to JSON" error. I tried the cycle.js, but when I decycle the 'ref', it goes decycled to the store. Is this good approach, maybe somewhere to retrocycle the 'ref' before adding it to the redux store?

Thanks a lot!

The ref will not serialize to JSON very well. I didn't have this problem, but I wasn't doing hot loading or state fast forwarding etc.

I wouldn't try to de-cycle the ref, it will probably introduce subtle bugs (It will remove the ref's root reference). Another approach is to store just url in the store, then reconstitute the Firebase ref when you need to act upon it:

// actions/firebaseRef.js

function setFirebaseRef(url) {
  return {
    type: 'FIREBASE_REF_SET',
    value: url
  };
}

In each of your actions just create the ref when needed:

// actions/config.js

import Firebase from 'firebase';

// ...

/**
 * Start listening to changes when the app boots
 */
function listenToConfigChanges() {
  return (dispatch, getState) => {
    const { firebaseRef } = getState();
    var ref = new Firebase(firebaseRef);

    ref.child('config').on('value', (snapshot) => {
      dispatch(replaceConfig(snapshot.val()));
    });
  };
}

/*
 * Save new config data
 */
function saveConfig(config) {
  return (dispatch, getState) => {
    const { firebaseRef } = getState();
    var ref = new Firebase(firebaseRef);

    ref.child('config').set(config);
} 

The Firebase refs are singletons, as long as the url is the same, the refs will be equivalent. e.g.

var ref1 = new Firebase('https://blah-demo.firebaseio.com');
var ref2 = new Firebase('https://blah-demo.firebaseio.com');

var handler = function () {};

ref1.on('value', handler);

ref2.off('value', handler); // this is fine

Shall we close? @erikras

We shall.

Would we need specific middleware to be able to load data server-side? How would we send back the response on the value event?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

eromoe picture eromoe  路  5Comments

chrisabrams picture chrisabrams  路  5Comments

c089 picture c089  路  5Comments

jorgehmv picture jorgehmv  路  3Comments

Dt-dev28 picture Dt-dev28  路  3Comments