8.4 KiB
Usage with Redux
Although RNFirebase usage requires a React Native environment, it isn't tightly coupled which allows for full flexibility
when it comes to integrating with other modules such a react-redux
.
React Redux Firebase
react-redux-firebase
provides simplified and standardized common redux/firebase logic.
To add react-redux-firebase
to your project:
- Run
npm i --save react-redux react-redux-firebase@canary
we point to canary here to get current progress with v2.0.0 - Add
firebaseStateReducer
underfirebase
key within reducer:
reducers.js
import { combineReducers } from 'redux';
import { firebaseStateReducer } from 'react-redux-firebase';
export const makeRootReducer = (asyncReducers) => {
return combineReducers({
// Add sync reducers here
firebase: firebaseStateReducer,
...asyncReducers
});
};
export default makeRootReducer;
// Useful for injecting reducers as part of async routes
export const injectReducer = (store, { key, reducer }) => {
store.asyncReducers[key] = reducer
store.replaceReducer(makeRootReducer(store.asyncReducers))
};
- Pass
react-native-firebase
instance intoreactReduxFirebase
when creating store:
createStore.js
import { applyMiddleware, compose, createStore } from 'redux';
import RNFirebase from 'react-native-firebase';
import { getFirebase, reactReduxFirebase } from 'react-redux-firebase';
import thunk from 'redux-thunk';
import makeRootReducer from './reducers';
const reactNativeFirebaseConfig = {
debug: true
};
// for more config options, visit http://docs.react-redux-firebase.com/history/v2.0.0/docs/api/compose.html
const reduxFirebaseConfig = {
userProfile: 'users', // save users profiles to 'users' collection
};
export default (initialState = { firebase: {} }) => {
// initialize firebase
const firebase = RNFirebase.initializeApp(reactNativeFirebaseConfig);
const middleware = [
// make getFirebase available in third argument of thunks
thunk.withExtraArgument({ getFirebase }),
];
const store = createStore(
makeRootReducer(),
initialState, // initial state
compose(
reactReduxFirebase(firebase, reduxFirebaseConfig), // pass initialized react-native-firebase app instance
applyMiddleware(...middleware)
)
);
return store;
};
- Wrap in
Provider
fromreact-redux
:
index.js
import React from 'react';
import { Provider } from 'react-redux';
import createStore from './createStore';
import Todos from './Todos';
// Store Initialization
const initialState = { firebase: {} };
let store = createStore(initialState);
const Main = () => (
<Provider store={store}>
<Todos />
</Provider>
);
export default Main;
- Then you can use the
firebaseConnect
HOC to wrap your components. It helps to set listeners which gather data from Firebase and place it into redux:
Home.js
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { isLoaded, isEmpty, firebaseConnect } from 'react-redux-firebase';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import NewTodo from './NewTodo';
import Todos from './Todos';
class Home extends React.Component {
state = {
text: null
}
completeTodo = (key, todo) => {
return this.props.firebase.update(`todos/${key}`, { done: !todo.done })
}
addTodo = () => {
const { text } = this.state;
return this.props.firebase.push('todos', { text, completed: false });
}
render() {
const { todos } = this.props;
return (
<View>
<Text>Todos</Text>
<NewTodo
onNewTouch={this.addTodo}
newValue={this.state.text}
onInputChange={(v) => this.setState({text: v})}
/>
{
!isLoaded(todos)
? <ActivityIndicator size="large" style={{ marginTop: 100 }}/>
: null
}
{
isLoaded(todos) && !isEmpty(todos)
?
<Todos
todos={todos}
onItemTouch={this.completeTodo}
/>
:
<View style={styles.container}>
<Text>No Todos Found</Text>
</View>
}
</View>
);
}
}
export default compose(
firebaseConnect([
// create listener for firebase data -> redux
{ path: 'todos', queryParams: ['limitToLast=15'] }
]),
connect((state) => ({
// todos: state.firebase.data.todos, // todos data object from redux -> props.todos
todos: state.firebase.ordered.todos, // todos ordered array from redux -> props.todos
}))
)(Home);
Todos.js
import React from 'react'
import {
View,
Text,
StyleSheet,
FlatList,
TouchableHighlight
} from 'react-native';
const Todos = ({ todos, onItemTouch }) => (
<FlatList
data={todos.reverse()}
renderItem={({ item: { key, value } }) => (
<TouchableHighlight onPress={() => onItemTouch(key, value)}>
<View>
<Text>{value.text}</Text>
<Text>Done: {value.done === true ? 'True' : 'False'}</Text>
</View>
</TouchableHighlight>
)}
/>
)
export default Todos;
Notice how connect
is still used to get data out of redux
since firebaseConnect
only loads data into redux.
Full source with styling available in the react-native-firebase example for react-redux-firebase
For more details, please visit react-redux-firebase
's react-native section.
Standalone Integration
Although the following example works for a basic redux setup, it may differ when integrating with other redux middleware. Imagine a simple TODO app, with redux we're able to abstract the Firebase logic out of components which allows for greater testability and maintainability.
?> We use redux-thunk
to provide async actions.
Action Creators
// Actions
export const subscribe = () => {
return (dispatch) => {
firebase.database().ref('todos').on('value', (snapshot) => {
const todos = [];
snapshot.forEach((childSnapshot) => {
todos.push({
id: childSnapshot.key,
...(childSnapshot.val()),
})
})
dispatch({
type: 'TODO_UPDATE',
todos,
})
})
}
}
// Methods
export const addTodo = text => {
firebase.database().ref('todos').push({
text,
visible: true,
})
}
export const completeTodo = id => {
firebase.database().ref(`todos/${id}`).update({
visible: false,
})
}
Instead of creating multiple actions which the reducers handle, we instead subscribe to the database ref and on any changes, send a single action for the reducers to handle with the data which is constantly updating.
Reducers
Our reducer now becomes really simple, as we're able to simply update the reducers state with whatever data has been returned from our Firebase subscription.
const todos = (state = {}, action) => {
switch (action.type) {
case 'TODO_UPDATE':
return { ...action.todos };
}
return state;
}
export default todos;
Component
We can now easily subscribe to the todos in redux state and get live updates when Firebase updates.
import React from 'react';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import { subscribe, addTodo, completeTodo } from '../actions/TodoActions.js';
...
class Todos extends React.Component {
componentDidMount() {
this.props.dispatch(
subscribe()
);
}
onComplete = (id) => {
this.props.dispatch(
completeTodo(id)
);
};
onAdd = (text) => {
this.props.dispatch(
addTodo(text)
);
};
render() {
return (
<FlatList
data={this.props.todos}
...
/>
);
}
}
function mapStateToProps(state) {
return {
todos: state.todos,
};
}
export default connect(mapStateToProps)(Todos);