0.0.12 • Published 7 years ago

redux-firebase-mirror v0.0.12

Weekly downloads
4
License
MIT
Repository
github
Last release
7 years ago

CircleCI codecov npm npm styled with prettier

redux-firebase-mirror

A library to help you easily mirror firebase data inside a redux store, and use that data inside a react application.

Table of Contents

  1. Installation
  2. Set Up
  3. API
    1. Configuration
    2. Action Creators
    3. Selectors
    4. Subscriptions
    5. React Higher-Order-Components

Installation

npm install redux-firebase-mirror redux redux-thunk firebase

Peer Dependencies

redux-firebase-mirror has the following peer and optional dependencies:

  • redux - this should be obvious. This library is designed to work with redux so you need to have it installed for this to be of any use.
  • firebase - Another obvious peer dependency. This library talks directly to firebase to fetch data.
  • redux-thunk - You must set up your redux store with the redux-thunk middleware which this library uses to execute asynchronous redux actions.

Set Up

Data from firebase will be mirrored in your redux state tree. For this to work, you must incorporate redux-firebase-mirror's redux reducer into your project's top-level redux reducer.

Use the combineReducers() utility function from redux to place the reducer under the firebaseMirror key. Here is an example of what this might look like:

import reduxFirebaseMirror from 'redux-firebase-mirror';
import {combineReducers, createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';

const app = firebase.initializeApp({...});
const firebaseMirror = reduxFirebaseMirror({
  getFirebase: () => app
});

const store = createStore(
  combineReducers({
    firebaseMirror,
    // your applications other reducers here...
  }),
  applyMiddleware(thunkMiddleware)
);

API

Configuration

reduxFirebaseMirror(config)

This function is used to initialize and configure the redux-firebase-mirror module and get a reducer to incorporate in your applications exisiting redux store. It takes an optional config object for customizing behavior.

ParamTypeDescription
configObjectconfiguration object. See options below.
config.getFirebaseFunctionfunction for getting the firebase App instance to use.
config.getFirebaseStateFunctionOptional selector function for getting the state used by redux-firebase-mirror. If not specified, will default to (state) => state.firebaseMirror;
config.persistToLocalStorageObjectA config object for persisting the mirror to local storage. If not provided, no data will be persisted. See below for the specific options
config.persistToLocalStorage.storagelocalStorage\|sessionStorageOptionally specify a custom storage system. Defaults to using localStorage. Can be any object which implements the same API as localStorage. Also supports async storage apis such as react native's AsyncStorage.
config.persistToLocalStorage.storagePrefixstringOptionally specify a prefix to use for all keys given to localStorage. Defaults to "firebase-mirror:".
config.syncIntervalnumberThe minimum number of milliseconds between syncs to the redux store. Defaults to 30

returns: a redux reducer function


Action Creators

The following action creators can be used to control what parts of the firebase database are being mirrored.

subscribeToValues(paths)

Registers listeners for firebase value events at the specified paths. Whenever the value changes in firebase, the redux store will be updated.

ParamTypeDescription
pathsPathSpec[]The array of firebase database paths to subscribe to. See the PathSpec documentation below for more details on how to configure filtering and ordering of the results returned by firebase.

returns: nothing

Example:

store.dispatch(subscribeToValues([
  `/profiles/${userId}`,
  `profilePics/${userId}/square`
]));
PathSpec

A PathSpec is either a string specifying the exact path to a location in the firebase database or an object containing a path along with additional configuration information. The configuration object has the following shape:

ParamTypeDescription
pathstringthe path to load from firebase
orderByKeybooleanIf true, results will be ordered by the key
orderByValuebooleanIf true, results will be ordered by value
orderByChildstringIf given, results will be ordered by the value of the child with the given key
filterObjectSee further configuration options below
filter.limitToLastnumberresults will be limited to the last N values
filter.limitToFirstnumberresults will be limited to the first N values
filter.startAtnumberresults will start at the given index
filter.endAtnumberresults will end at the given index
filter.equalToanyresults will match the given value

For example, you can use PathSpec configuration objects to subscribe to the last 10 messages in an inbox for a given user:

subscribeToValues([
  {
    path: '/messages',
    orderByChild: 'sentTo',
    filter: {
      equalTo: userId,
      limitToLast: 10,
    },
  }
]);

unsubscribeFromValues(paths)

Stops listening to firebase value events at the specified paths. The mirror will no longer be updated at this location unless there is another listener at a higher path. Note that whatever data was already mirrored will still be available in the redux state.

ParamTypeDescription
pathsstring[]The array of firebase database paths to subscribe to.

returns: nothing

Example:

store.dispatch(unsubscribeFromValues([
  `/profiles/${userId}`,
  `profilePics/${userId}/square`
]));

fetchValues(paths, callback)

Mirrors the values at the specified paths in the firebase database into your redux store once. The mirrored data will not continue to sync with firebase.

ParamTypeDescription
pathsstring[]The array of firebase database paths to fetch.
callback[Function]Optional callback function which will be called once the data has been synced.

returns: a Promise that resolves once the data has been synced.

Example:

store.dispatch(fetchValues([
  `/profiles/${userId}`,
  `profilePics/${userId}/square`
])).then(() => {
  console.log("received profile data", getValueAtPath(store.getState(), `/profiles/${userId}`));
  console.log("profile picture is", getValueAtPath(store.getState(), `profilePics/${userId}/square`));
});

Selectors

The following selector functions can be used to query the mirror.

getValueAtPath(state, path)

Returns the value at the given firebase database path, as it was most recently mirrored, as an Immutable object.

ParamTypeDescription
stateRedux StateThe redux store's state, i.e. store.getState()
pathstringThe firebase database path to get.

Returns: undefined | boolean | number | string | Immutable.List | Immutable.Map - The immutable value at the given path as an Immutable object, or undefined if there is nothing there.

Example:

const userProfile = getValueAtPath(store.getState(), '/profiles/${userId}');
console.log("user is", userProfile.get("name"));

getKeysAtPath(state, path)

Similar to getValueAtPath, but only returns an array of keys at the path.

ParamTypeDescription
stateRedux StateThe redux store's state, i.e. store.getState()
pathstringThe firebase database path to get.

Returns: string[] - The list of keys at the given database path.

Example:

const userIds = getKeysAtPath(store.getState(), '/profiles');
userIds.forEach(userId => {
  console.log(
    "user", userId,
    "is named", getValueAtPath(store.getState(), `/profiles/${userId}`).get("name"),
  );
});

Subscriptions

To make subscribing to and querying the same set of paths all over the place easier, you can use the Subscription class.

new Subscription({paths, value})

Creates a new Subscription object which describes how to fetch and interpret values in the database.

ParamTypeDescription
configObjectA configuration object for the subscription. See options below.
config.pathsFunctionA function that maps state and props to an array of paths or PathSpec config objects.
config.valueFunctionA function that maps state and props to a particular value in the database.

Example:

const profileById = new Subscription({
  paths: (state, props) => [`/profiles/${props.userId}`],
  value: (state, props) => getValueAtPath(state, `/profiles/${props.userId}`),
});

Example of a fanout query:

const friendIds = new Subscription({
  paths: profileById.paths,
  value: (state, props) => getKeysAtPath(state, `/profiles/${props.userId}/friends`),
});

const profilePicById = new Subscription({
  paths: (state, props) => [`/profilePics/${props.userId}/${props.size}`],
  value: (state, props) => (
    getValueAtPath(state, `/profilePics/${props.userId}/${props.size}`) ||
    '/static/silhouette.png'
  )
});

const friendProfilePics = new Subscription({
  paths: (state, props) => [
    ...friendIds.paths(state, props)];
    ...friendIds.value(state, props).map(
      friendId => profilePicById.path(state, {userId: friendId, size: props.size})
    )
  ],
  value: (state, props) => {
    return friendIds.value(state, props).map(
      friendId => profilePicById.value(state, {userId: friendId, size: props.size})
    );
  }
});

Subscription.fetchNow(store, props)

Will fetch all the data needed by the subscription (filling the mirror in the process) and resolve the returned promise with the subscription's resulting value.

ParamTypeDescription
storeRedux StateThe redux store.
propsObjectWhatever props are needed by the subscription's paths and value functions.

Returns: a Promise that resolves to whatever the value function returns.

Example:

friendProfilePics
  .fetchNow(store, {userId: getLoggedInUser(store.getState()), size: 'small'})
  .then(urls => {
    console.log("profile pictures of your friends can be found at the following urls:", urls.join('\n'));
  });

Subscription.mapProps(mapper)

Creates a new subscription object that can handle props with different names. Useful in conjunction with the react higher order components.

ParamTypeDescription
mapperFunctionA function that translates from one prop format to another.

returns: a new Subscription instance.

Example:

// let me pass in userId as id instead
friendProfilePics.mapProps(({id}) => ({userId: id}));

React Higher-Order-Components

subscribePaths(mapPropsToPaths)

Wraps a react component such that the given paths are subscribed to when the component is mounted.

ParamTypeDescription
mapPropsToPathsFunctionA function that maps the current redux state and the component's props to an array of paths.

Example:

const FundraisingMeter = compose(
  subscribePaths(
    (state, props) => ['/fundraising'],
  ),
  connect(
    (state, props) => ({fund: getValueAtPath(state, '/fundraising')})
  )
)(class extends Component {
  render() {
    if (!this.props.fund) {
      return <span>Loading...</span>;
    }
    const remaining = this.props.fund.get("goal") - this.props.fund.get("current");
    return (
      <span>
        We are ${remaining} dollars away from our goal!
      </span>
    );
  }
});

subscribeProps(mapPropsToSubscriptions)

Similar to subscribePaths, but takes a mapping from props to a collection of subscription objects and populates the given components props with the values from the subscriptions.

ParamTypeDescription
mapPropsToSubscriptionsFunction\|ObjectA function that maps the current redux state and the component's props to an array of paths. Alternatively, you can also just pass in an object if you don't care about the state or props.
class ImageCollage extends Component {
  static propTypes = {
    imgUrls: PropTypes.arrayOf(PropTypes.string),
  };
  render() {
    return (
      <div>
        {this.props.imgUrls.map(imgUrl => <img src={imgUrl} />)}
      </div>
    );
  }
}

const FriendCollageContainer = subscribeProps({
  imgUrls: friendProfilePics,
})(ImageCollage);

Rehydration from stored state

If you are using redux-firebase-mirror in an isomorphic react application with subscriptions occuring on both the client and the server, then you will probably want to populate the client state with data already fetched by the server. You can do this using the following selector and action:

getDehydratedState(state)

Returns a serializable version of the mirrored state used by redux-firebase-mirror.

ParamTypeDescription
stateRedux StateThe redux store's state, i.e. store.getState()

rehydrate(data)

This action creator will update the store with the rehydrated data.

ParamTypeDescription
dataObjectThe data with which to rehydrate the state. i.e. the result of getDehydratedState

See more information about state rehydration in redux in the redux documetation.

0.0.12

7 years ago

0.0.11

7 years ago

0.0.10

7 years ago

0.0.9

7 years ago

0.0.8

7 years ago

0.0.7

7 years ago

0.0.6-beta.3

7 years ago

0.0.6-beta.2

7 years ago

0.0.6-beta.1

7 years ago

0.0.5

7 years ago

0.0.4

7 years ago