10 min read

This is the second part in a series on using Redux and Firebase with React. If you haven’t read through the first part, then you should go back and do so, since this post will build on the other. If you have gone through the first part, then you’re in the right place.

In this final part of this two-part series, you will be creating actions in your app to update the store. Then you will create a Firebase app and set up async actions that will subscribe to Firebase. Whenever the data on Firebase changes, the store will automatically update and your app will receive the latest state. After all of that is wired up, you’ll create a quick user interface. Lets get started.

Creating Actions in Redux

For your actions, you are going to create a new file inside of src/:

[~/code/yak]$ touch src/actions.js

Inside of actions.js, you will create your first action; an action in Redux is simply an object that contains a type and a payload. The type allows you to catch the action inside of your reducer and then use the payload to update state. The type can be a string literal or a constant. I recommend using constants for the same reasons given by the Redux team. In this app you will set up some constants to follow this practice.

[~/code/yak]$ touch src/constants.js

Create your first constant in src/constants.js:

export const SEND_MESSAGE = 'CHATS:SEND_MESSAGE';

It is good practice to namespace your constants, like the constant above the CHATS. Namespace can be specific to the CHATS portion of the application and helps document your constants. Now go ahead and use that constant to create an action. In src/actions.js, add:

import { SEND_MESSAGE } from './constants';

export function sendMessage(message) {
  return {
    type: SEND_MESSAGE,
    payload: message
  };
}

You now have your first action! You just need to add a way to handle that action in your reducer. So open up src/reducers.js and do just that. First import the constant.

import { SEND_MESSAGE } from './constants';

Then inside of the yakApp function, you’ll handle different actions:

export function yakApp(state = initialState, action) {
  switch(action.type) {
    case SEND_MESSAGE:
      return Object.assign({}, state, {
        messages: state.messages.concat([action.payload])
      });
    default:
      return state;
  }
}

A couple of things are happening here, you’ll notice that there is a default case that returns state, your reducer must return some sort of state. If you don’t return state your app will stop working. Redux does a good job of letting you know what’s happening, but it is still good to know.

Another thing to notice is that the SEND_MESSAGE case does not mutate the state. Instead it creates a new state and returns it. Mutating the state can result in bad side effects that are hard to debug; do your very best to never mutate the state.

You should also avoid mutating the arguments passed to a reducer, performing side effects and calling any non-pure functions within your reducer. For the most part, your reducer is set up to take the new state and return it in conjunction with the old state.

Now that you have an action and a reducer that is handling that action, you are ready for your component to interact with them. In src/App.js add an input and a button:

<p className="App-intro">
  <input type="text" />{' '}
  <button>Send Message</button>
</p>

Now that you have the input and button in, you’re going to add two things. The first is a function that runs when the button is clicked. The second is a reference on the input so that the function can easily find the value of your input.

js <p className="App-intro">   <input type="text" ref={(i) => this.message = i}/>{' '}   <button onClick={this.handleSendMessage.bind(this)}>Send Message</button> </p>

Now that you have those set, you will need to add the sendMessage function, but you’ll also need to be able to dispatch an action. So you’ll want to map your actions to props, similar to how you mapped state to props in the previous guide. At the end of the file, under the mapStateToProps function, add the following function:

function mapDispatchToProps(dispatch) {
  return {
    sendMessage: (msg) => dispatch(sendMessage(msg))
  };
}

Then you’ll need to add the mapDispatchToProps function to the export statement:

export default connect(mapStateToProps, mapDispatchToProps)(App);

And you’ll need to import the sendMessage action into src/App.js:

import { sendMessage } from './actions';

Finally you’ll need to create the handleSendMessage method inside of your App class, just above the render method:

handleSendMessage() {
  const { sendMessage } = this.props;
  const { value } = this.message;
  if (!value) {
    return false;
  }

  sendMessage(value);
  this.message.value = '';
}

If you still have a console.log statement inside of your render method, from the last guide, you should see your message being added to the array each time you click on the button.

The final piece of UI that you need to create is a list of all the messages you’ve received. Add the following to the render method in src/App.js, just below the <p/> that contains your input:

{messages.map((message, i) => (
  <p key={i} style={{textAlign: 'left', padding: '0 10px'}}>{message}</p>
))}

Now you have a crude chat interface; you can improve it later.

Set up Firebase

If you’ve never used Firebase before, you’ll first need a Google account. If you don’t have a Google account, sign up for one; then you’ll be able to create a new Firebase project.

Head over to Firebase and create a new project. If you have trouble naming it, try YakApp_[YourName].

After you have created your Firebase project, you’ll be taken to the project. There is a button that says “Add Firebase to your web app”; click on it and you’ll be able to get the configuration information that you will need for your app to be able to work with Firebase. A dialog will open and you should see something like this:

You will only be using the database for this project, so you’ll only need a portion of the config. Keep the config handy while you prepare the app to use Firebase. First add the Firebase package to your app:

[~/code/yak]$ npm install --save firebase

Open src/actions.js and import firebase:

import firebase from 'firebase'

You will only be using Firebase in your actions; that is the reason you are importing it here. Once imported, you’ll need to initialize Firebase. After the import section, add your Firebase config (the one from the image above), initialize Firebase and create a reference to messages:

const firebaseConfig = {
  apiKey: '[YOUR API KEY]',
  databaseURL: '[YOUR DATABASE URL]'
}
firebase.initializeApp(firebaseConfig);
const messages = firebase.database().ref('messages');

You’ll notice that you only need the apiKey and databaseUrl for now. Eventually you might want to add auth and storage, and other facets of Firebase, but for now you only need database. Now that Firebase is set up and initialized, you can use it to store the chat history.

One last thing before you leave the Firebase console: Firebase automatically sets the database rules to require users to be logged in. That is a great idea, but authentication is outside of the scope of this post. So you’ll need to turn that off in the rules. In the Firebase console, click on the “Database” navigation item on the left side of the page; now there should be a tab bar with a “Rules” option. Click on “Rules” and replace what is in the textbox with:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Create subscriptions

In order to subscribe to Firebase, you will need a way to send async actions. So you’ll need another package to help with that. Go ahead and install redux-thunk.

[~/code/yak]$ npm install --save redux-thunk

After it’s finished installing, you’ll need to add it as middleware to your store. That means it’s time to head back to src/index.js and add the extra parameters to the createStore function. First import redux-thunk into src/index.js and import another method from redux itself.

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

Now update the createStore call to be:

const store = createStore(yakApp, undefined, applyMiddleware(thunk));

You are now ready to create async actions. In order to create more actions, you’re going to need more constants. Head over to src/constants.js and add the following constant.

export const RECEIVE_MESSAGE = 'CHATS:RECEIVE_MESSAGE';

This is the only constant you will need for now; after this guide, you should create edit and delete actions. At that point, you’ll need more constants for those actions. For now, only concern yourself with adding and subscribing.

You’ll need to refactor your actions and reducer to handle these new features. In src/actions.js, refactor the sendMessage action to be an async action. The way redux-thunk works is that it intercepts any action that is dispatched and inspects it. If the action returns a function instead of an object, redux-thunk stops it from reaching the reducer and runs the function. If the action returns an object, redux-thunk ignores it and allows it to continue to the reducer. Change sendMessage to look like the following:

export function sendMessage(message) {
  return function() {
    messages.push(message);
  };
}

Now when you type a message in and hit the “Submit Message” button, the message will get stored in Firebase. Try it out!

UH OH! There’s a problem! You are adding messages to Firebase but they aren’t showing up in your app anymore! That’s ok. You can fix that! You’ll need to create a few more actions though, and refactor your reducer. First off, you can delete one of your constants. You no longer need the constant from earlier:

export const SEND_MESSAGE = 'CHATS:SEND_MESSAGE';

Go ahead and remove it; that leaves you with only one constant. Change your constant import in both src/actions.js and src/reducer.js:

import { RECEIVE_MESSAGE } from './constants';

Now in actions, add the following action:

function receiveMessage(message) {
  return {
    type: RECEIVE_MESSAGE,
    payload: message
  };
}

That should look familiar; it’s almost identical to the original sendMessage action. You’ll also need to rename the action that your reducer is looking for. So now your reducer function should look like this:

export function yakApp(state = initialState, action) {
  switch(action.type) {
    case RECEIVE_MESSAGE:
      return Object.assign({}, state, {
        messages: state.messages.concat([action.payload])
      });
    default:
      return state;
  }
}

Before the list starts to show up again, you’ll need to create a subscription to Firebase. Sounds more complicated than it really is. Add the following action to src/actions.js:

export function subscribeToMessages() {
  return function(dispatch) {
    messages.on('child_added', data => dispatch(receiveMessage(data.val())));
  }
}

Now, in src/App.js you’ll need to dispatch that action and set up the subscription. First change your import statement from ./actions to include subscribeToMessages:

import { sendMessage, subscribeToMessages } from './actions';

Now, in mapDispatchToProps you need to map subscribeToMessages:

function mapDispatchToProps(dispatch) {
  return {
    sendMessage: (msg) => dispatch(sendMessage(msg)),
    subscribeToMessages: () => dispatch(subscribeToMessages()),
  };
}

Finally, inside of the App class, add the componentWillMount life cycle method just above the handleSendMessage method, and call subscribeToMessages:

componentWillMount() {
  this.props.subscribeToMessages();
}

Once you save the file, you should see that your app is subscribed to the Firebase database, which is automatically updating your Redux state, and your UI is displaying all the messages stored in Firebase. Have some fun and open another browser window!

Conclusion

You have now created a React app from scratch, added Redux to it, refactored it to connect to Firebase and, via subscriptions, updated your entire app state!

What will you do now? Of course the app could use a better UI; you could redesign it! Maybe make it a little more usable. Try learning how to create users and show who yakked about what! The sky is the limit, now that you know how to put all the pieces together! Feel free to share what you’ve done with the app in the comments!

About the author

AJ Webb is team lead and frontend engineer for @tannerlabs and a co-creator of Payba.cc.

LEAVE A REPLY

Please enter your comment!
Please enter your name here