FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade
Welcome to FullStack Labs. We use cookies to enable better features on our website. Cookies help us tailor content to your interests and locations and provide many other benefits of the site. For more information, please see our Cookies Policy and Privacy Policy.

Creating a React Native App With a Ruby On Rails Backend (Part 2 of 3)

Written by 
,
Creating a React Native App With a Ruby On Rails Backend (Part 2 of 3)
blog post background
Recent Posts
Six Ways to Handle Concurrency in the JVM
Is there value in learning Vanilla JavaScript?
How to be efficient in remote design work in multicultural teams

In the previous article, we set up the Ruby on Rails API. Now, we are ready to set up our React Native app and implement a state management system. For this tutorial, we will use the 0.60.5 version of React Native and Redux.

Table of contents

Note: Don’t forget to keep the Ruby on Rails API running in a separate terminal tab so you can test your connection throughout the tutorial.

Setting Up a React Native App

The process for setting up your React Native app will vary depending on your computer’s operating system and the platform you choose for rendering the app.

First, run cd .. to go to the project’s root directory. Then, go to React Native’s Getting Started guide. Click on the React Native CLI Quickstart tab, and follow the instructions. Make sure you are viewing instructions for the OS (MacOS, Windows, or Linux) and platform (Android or iOS) combination you will be using to render the app.

When you arrive at the Creating a new application section, do not install the most recent version of React Native. Instead, install the 0.60.5 version using this command:

-- CODE language-jsx keep-markup --
react-nativeinitnoteApp--versionreact-native@0.60.5

Continue following the steps in the React Native guide until you see the Welcome Screen on your emulator or device.

Installing Redux

Redux manages app-wide state in a single store object that is passed to specific components, usually called connected components. The data passed in from the store object is then passed down to lower level components as props from connected components. Actions, with ActionTypes, are dispatched to Reducers that respond by updating the state of the app. To learn more about Redux, take a look at the Redux docs.

We also need to install redux-thunk, a middleware that will allow us to make asynchronous requests in our actions.

Open package.json and add the following libraries to the dependencies section:

-- CODE language-jsx keep-markup --
"dependencies": {  
  "react": "16.8.6",  
  "react-native": "0.60.5",  
  "react-redux": "^6.0.0",  
  "redux": "^4.0.1",  
  "redux-thunk": "^2.3.0"
},

Run npm i or yarn to install these new dependencies. Add these libraries to the app by updating App.js as follows:

-- CODE language-jsx keep-markup --
import React from 'react';
import { View } from 'react-native';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { applyMiddleware, combineReducers, createStore } from 'redux';
import reducers from './app/reducers';

const App = () => {  
  return (    
    <view>  </view>
  );
};

export default App;

We will use the combineReducers function to combine all app reducers into a single reducer, called rootReducer.

-- CODE language-jsx keep-markup --
const rootReducer = combineReducers({...reducers});

Next, we will create a store using the createStore function. This function will take the rootReducer and applyMiddleware functions as parameters. To set it up, pass thunk into applyMiddleware.

-- CODE language-jsx keep-markup --
const store = createStore(rootReducer, applyMiddleware(thunk));

Finally, pass the store object into the <provider></provider> component and wrap the <view> </view>in this component

-- CODE language-jsx keep-markup --
import React from 'react';
import {View} from 'react-native';
import thunk from 'redux-thunk';
import {Provider} from 'react-redux';
import {applyMiddleware, combineReducers, createStore} from 'redux';
import reducers from './app/reducers';

const rootReducer = combineReducers({...reducers});
const store = createStore(rootReducer, applyMiddleware(thunk));

const App = () => {  
  return (    
    <provider store="{store}"></provider>
      <view></view>
      
  );
};
exportdefault App;

Creating ActionTypes

A typical redux folder structure separates actions, actionTypes, and reducers into separate folders. This project will follow a similar structure.

-- CODE language-jsx keep-markup --
app/  
  actions/      
    noteActions.js  
  actionTypes/      
    notes.js  
  reducers/      
    index.js      
    notes.js

Create the files referenced above with your editor, or use the following command:

-- CODE language-jsx keep-markup --
$ mkdir -p app/actions/ app/actionTypes/ app/reducers/
$ touch app/actions/noteActions.js app/actionTypes/notes.js app/reducers/index.js app/reducers/notes.js

Let’s take a look at the app/actionTypes/notes.js file to declare our action types. Our app needs to perform two primary actions: fetching and creating notes. Each action requires three different states to represent the status of the request: loading, success, and failure. Therefore, we need to create a total of six actionTypes. In the app/actionTypes/notes.js file, create the corresponding actionTypes.

-- CODE language-jsx keep-markup --
exportconst FETCH_NOTES = 'FETCH_NOTES';
exportconst FETCH_NOTES_SUCCESS = 'FETCH_NOTES_SUCCESS';
exportconst FETCH_NOTES_FAILURE = 'FETCH_NOTES_FAILURE';
exportconst CREATE_NOTE = 'CREATE_NOTE';
exportconst CREATE_NOTE_SUCCESS = 'CREATE_NOTE_SUCCESS';
exportconst CREATE_NOTE_FAILURE = 'CREATE_NOTE_FAILURE';

Now, we need to create the actions that will fire these actionTypes. Open app/actions/notesActions.js and import the actionTypes we defined above.

-- CODE language-jsx keep-markup --
import * as types from '../actionTypes/notes';

Creating Actions

To create the action fetchNotes() and dispatch the FETCH_NOTES actionType, add the following to the app/actions/notesActions.js file:

-- CODE language-jsx keep-markup --
exportfunctionfetchNotes() {  
  returnasync dispatch => {    
    dispatch({type: types.FETCH_NOTES});  
  };
}

Then, use the fetch function to call the notes endpoint on the Rails API. If the response is successful, use the .json() function to convert the content to JSON. If the response is not successful, throw an error.

-- CODE language-jsx keep-markup --
exportfunctionfetchNotes() {  
  returnasync dispatch => {    
    dispatch({type: types.FETCH_NOTES});      
    
      let response = await fetch('http://localhost:5000/notes');      
        if (response.status !== 200) {        
          thrownew Error('FETCH_ERROR');      
        }      
        response = await response.json();      
        dispatch({type: types.FETCH_NOTES_SUCCESS, data: response});  
  };
};

Wrap the request in a try/catch block to capture any failures, and dispatch the FETCH_NOTES_FAILURE actionType when a failure is detected.

-- CODE language-jsx keep-markup --
exportfunctionfetchNotes() {  
  returnasync dispatch => {    
    dispatch({type: types.FETCH_NOTES});    
    try {      
      let response = await fetch('http://localhost:5000/notes');      
      if (response.status !== 200) {        
        thrownew Error('FETCH_ERROR');      
      }      
      response = await response.json();      
      dispatch({type: types.FETCH_NOTES_SUCCESS, data: response});    
    } catch (error) {      
      dispatch({type: types.FETCH_NOTES_FAILURE, error});    
    }  
  };
}

With the fetch action complete, we can now implement the createNote action. Add the following to the app/actions/notesActions.js file:

-- CODE language-jsx keep-markup --
exportfunctioncreateNote(note) {  
  returnasync dispatch => {    
    dispatch({type: types.CREATE_NOTE});    
    try {      
      let response = await fetch('http://localhost:5000/notes', {        
        method: 'POST',        
        headers: {          
          Accept: 'application/json',          
          'Content-Type': 'application/json',        
        },        
        body: JSON.stringify({note}),      
    });      
    if (response.status !== 200) {        
      thrownew Error('FETCH_ERROR');      
    }      
    response = await response.json();      
    dispatch({type: types.CREATE_NOTE_SUCCESS, data: response});    
    } catch (error) {      
      dispatch({type: types.CREATE_NOTE_FAILURE, error});    
    }  
  };
}

The only difference between the createNote action and the fetchNotes action is the fetch configuration because we need to send the note data that we intend to create. The fetch HTTP method should be changed to POST and the body property should contain the note data, converted to a JSON string.

-- CODE language-jsx keep-markup --
let response = await fetch('http://localhost:5000/notes', {  
  method: 'POST',  
  headers: {    
    Accept: 'application/json',    
    'Content-Type': 'application/json',  
  },  
  body: JSON.stringify({note}),
});

Setting Up the Reducer

Reducers require an initial state that defines all values that will be used into the store. Open app/reducers/notes.js and create a simple object:

-- CODE language-jsx keep-markup --
const INITIAL_STATE = {  
  data: [],  
  status: null,  
  error: null,  
  createStatus: null,  
  createError: null,
};

The data property stores the notes we get from the fetchNotes action. The status and error properties handle the different states of the fetchNotes request. Finally, the createStatus and createError handle the statuses of the createNote request.

Below the INITIAL_STATE object, create a basic reducer function:

-- CODE language-jsx keep-markup --
const INITIAL_STATE = {  
  data: [],  
  status: null,  
  error: null,  
  createStatus: null,  
  createError: null,
};

exportdefault (state = INITIAL_STATE, action) => {  
  switch (action.type) {    
    default:      
      return state;  
  }
};

The parameter action passes in the action data and type returned from a dispatched action. Let's create the first case for the fetchNotes action:

-- CODE language-jsx keep-markup --
exportdefault (state = INITIAL_STATE, action) => {  
  switch (action.type) {    
    case 'FETCH_NOTES':      
      return {        
        ...state,        
        status: 'loading',        
        error: null,      
      };    
    default:      
      return state;  
  }
};

Each case returns the full app state, which means we need to destructure the current state along with new properties to preserve the full state. Next, create the remaining cases for the fetchNotes action:

-- CODE language-jsx keep-markup --
exportdefault (state = INITIAL_STATE, action) => {  
  switch (action.type) {    
    case 'FETCH_NOTES':      
      return {        
        ...state,        
        status: 'loading',        
        error: null,
      };    

    case 'FETCH_NOTES_SUCCESS':      
      return {        
        ...state,        
        status: 'success',        
        data: action.data,        
        error: null,      
      };    

    case 'FETCH_NOTES_FAILURE':      
      return {        
        ...state,        
        status: 'failure',        
        error: action.error,      
      };    
    default:      
      return state;  
  }
};

FETCH_NOTES is dispatched before our fetch call to the API and ther and sets loading to true so we can display the loading state in the UI.

FETCH_NOTES_SUCCESS is dispatched on success and adds the returned notes to the store.

FETCH_NOTES_FAILURE is dispatched when the action fails and sets the error property to the error returned.

Next, create the createNote cases:

-- CODE language-jsx keep-markup --
switch (action.type) {  
  ...    
  case 'CREATE_NOTE':    
    return {      
      ...state,      
      createStatus: 'loading',      
      createError: null,    
    };  

    case 'CREATE_NOTE_SUCCESS':    
      return {      
        ...state,      
        createStatus: 'success',      
        data: [...state.data, action.data],      
        createError: null,    
      };  
    
    case 'CREATE_NOTE_FAILURE':    
      return {      
        ...state,      
        createStatus: 'failure',      
        createError: action.error,    
      };  
    ...
}

The initial and failure case are similar to the corresponding fetchNotes cases. The CREATE_NOTE_SUCCESS case, however, appends the newly created note to the end of the list of notes. This saves us from making another fetchNotes call to refresh the list of notes with the new note:

-- CODE language-jsx keep-markup --
data: [...state.data, action.data],

Finally add our reducer to the index file, app/reducers/index.js:

-- CODE language-jsx keep-markup --
import notes from './notes';

exportdefault {  
  notes,
};

We have now created a React Native app, set up Redux, created actions, and created a reducer. In the final article of this series, we build the UI and connect it with Redux.

Check out the Github repo for this tutorial to see the completed app.

---

At FullStack Labs, we pride ourselves on our ability to push the capabilities of cutting-edge frameworks like React. Interested in learning more about speeding up development time on your next project? Contact us.

Written by
People having a meeting on a glass room.
Join Our Team
We are looking for developers committed to writing the best code and deploying flawless apps in a small team setting.
view careers
Desktop screens shown as slices from a top angle.
Case Studies
It's not only about results, it's also about how we helped our clients get there and achieve their goals.
view case studies
Phone with an app screen on it.
Our Playbook
Our step-by-step process for designing, developing, and maintaining exceptional custom software solutions.
VIEW OUR playbook
FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project.
Engagements start at $75,000.

company name
name
email
phone
Type of project
How did you hear about us?
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.