Blog

How to Use Backendless with ReactJS

by on March 21, 2019

ReactJS is one of the best and most popular frontend frameworks available for app builders. The barriers to entry in terms of understanding how to develop an app with ReactJS are very low, which is why many JavaScript developers choose the ReactJS library for web or mobile applications. It also works very well with a large number of data providers. Therefore, today we are beginning a series of articles about “How to use Backendless with ReactJS”. 

In this series, we will familiarize you with a number of Backendless features, show you how to use our real-time database with React-Redux store, show you how to deploy your app to Backendless File Storage, and demonstrate how to easily inject the JS-SDK into your ReactJS application.
Create a Web App Using React and Backendless
If you have experience with AngularJS and would like to learn how to build an app with Backendless using that language, you can check out our previous series of articles:

In this article, as was the case with our Angular series, we will start by creating a new Backendless App and building a simple React app. Our demo app will be an Address Book app, so to get started we will show how to load and display some data from the server. In the future, we will modernize the application by adding more functionality.

Let’s get started!

Setup Backendless

Continuing our old tradition, before we get going let’s make sure you have a Backendless account. If you don’t have one already, just register for new free account here.
Register for Backendless Developer Account
Now let’s create a new Backendless App. I will call my app “ReactArticle”, but you can choose any name you wish.

Create React App

As an entry point for creating a new app, we will use this post from the official ReactJS site. I’ve highlighted the most important steps for our app:

  • Create a new React App using create-react-app with the following command in your terminal:
npx create-react-app backendless-react
  • Go to the directory and run your app:
cd backendless-react
npm start

Once the application is generated, it will automatically open a new page in your browser with the url “http://localhost:3000″:
Preview web app with ReactJS
That’s it, very easy, right?

Add Styles

For styling in our app, we will use the Bootstrap framework. This will give us a high-quality and modern “look and feel” and it can be injected into the app very quickly.

Just add one line into ./public/index.html:

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">

Let’s change the render method in the App.js file; just replace the returned JSX with the following one:

render() {
return (
 <div className="container">
  <div className="header">
   <h3>
    Backendless Addresses Book
   </h3>
  </div>
 </div>
);
}

Also, you can remove the App.css file as we don’t need it anymore.

Setup Data Store

Right now our app is very primitive, but in future, it will be more complex. So now it’s time to add a central storage for sharing data between app components. To do this, we will use React-Redux.

Install the module with the following command:

npm i redux react-redux redux-thunk -S

Let’s also install redux-logger for logging all changes in the store as a dev dependency:

npm i redux-logger -D

Here are a few important things you should be aware of when your app is based on redux store:

  • Immutable store – Store is just a plain object and passes complex values by link, so be careful not to modify store values directly in components. Instead, always do it with calling actions.
  • Actions – Actions are commands for store modification. Each action has a unique type and may have some additional data, and according to action type and action data reducer, may change the current state of the store or do nothing. It depends on your code in your reducers, which we’ll discuss more later.
  • Reducers – Reducers are functions that are called each time you fire a store action. Reducers accept the current state of the store and action object and return a new state object or may just return the current state without any changes.
  • Selectors – Selectors are functions-shortcuts that help you get needed data from the store. Selectors are very helpful when you’ve got a big store with a complex structure.

As you recall, we are developing a simple Address Book app, so let’s prepare basic store actions/reducers/selectors. To do so, we have to create several files:
Create store actions, reducers, and selectors
Now we are going to copy/paste some code. We will explain why each folder/file is needed as we go.

./store/index.js

This is the re-export actions/selectors and createStore function.

export * from './create-store';
export * from './reducers';
export * from './actions';

./store/create-store.js

Here we will configure the app store and, as you may have noticed, there is some apiCall middleware that is needed for processing async actions. We will come back to the middleware a little bit later.

import { createStore as createReduxStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import rootReducer from './reducers';
export const createStore = () => createReduxStore(
rootReducer,
applyMiddleware(
  thunkMiddleware,
  apiCall,
  createLogger({ collapsed: true })
)
);
function apiCall({ dispatch }) {
return next => action => {
  if (!action.apiCall) {
    return next(action)
  }
  const { apiCall, types = [], ...restAction } = action;
  const [REQUEST, REQUEST_SUCCESS, REQUEST_FAIL] = types;
  if (REQUEST) {
    dispatch({ type: REQUEST, ...restAction })
  }
  return apiCall()
    .then(result => {
      if (REQUEST_SUCCESS) {
        dispatch({ type: REQUEST_SUCCESS, ...restAction, result })
      }
    })
    .catch(error => {
      console.error('An error occurred.', error);
      if (REQUEST_FAIL) {
        dispatch({ type: REQUEST_FAIL, ...restAction, error: error.message, errorCode: error.code })
      }
      throw error
    });
}
}

./store/action-types.js

In this file, we will keep all of the action types and, to make it cleaner, we have the mirrorKeys function. With the mirrorKeys function, we don’t need to duplicate the action type value since the function does it for us.

export default mirrorKeys({
LOAD_PERSONS        : null,
LOAD_PERSONS_SUCCESS: null,
LOAD_PERSONS_FAIL   : null,
});
function mirrorKeys(obj) {
const mirroredObject = {};
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    mirroredObject[key] = key
  }
}
return mirroredObject
}

At the end of the file, we will export an object where key and value are equal.

export default {
LOAD_PERSONS        : 'LOAD_PERSONS',
LOAD_PERSONS_SUCCESS: 'LOAD_PERSONS_SUCCESS',
LOAD_PERSONS_FAIL   : 'LOAD_PERSONS_FAIL',
}

./store/actions/index.js

This will re-export all of the actions for quick access in components.

export * from './persons';

./store/actions/persons.js

In this file, we will keep all of the actions related to the Persons data. For now, we only have actions.

import t from '../action-types';
const DUMMY_PERSONS = [
{ name: 'Tony Stark', address: '10880 Malibu Point' },
{ name: 'Bruce Banner', address: '2766 Taylor Street' },
{ name: 'Steve Rogers', address: 'New York City, Brooklyn' },
{ name: 'Thor Odinson', address: 'Palace in Asgard' }
];
export const loadPersons = () => ({
types  : [t.LOAD_PERSONS, t.LOAD_PERSONS_SUCCESS, t.LOAD_PERSONS_FAIL],
apiCall: () => Promise.resolve(DUMMY_PERSONS),
});

As you can see here, there are three action types:

  • LOAD_PERSONS – start loading Persons
  • LOAD_PERSONS_SUCCESS – Persons has been loaded
  • LOAD_PERSONS_FAILs – could not load Persons

This is because we will load our persons from the server, which means we will have an async function and that should cover all states. This is possible because of the apiCall store middleware that we have in the  ./store/create-store.js file.

./store/reducers/index.js

Here we combine the app root reducer and define store selectors for quick access to store data. Some may recommend locating store selectors in a separate file/directory, but I prefer to have them with the reducers.

import { combineReducers } from 'redux'
import persons from './persons'
const rootReducer = combineReducers({
persons
});
export default rootReducer;
export const getPersons = state => state.persons;

./store/reducers/persons.js

Here is the actual persons reducer. Thanks to both reduceReducers and loadReducer helpers, the reducer is as small as it is. In short, what is happening here is that each time we load persons, the “list” array will be replaced with a new one from the server.

import t from '../action-types'
import { reduceReducers, loadReducer } from './helpers'
const initialState = {
list: []
};
const personsReducer = reduceReducers(initialState,
loadReducer(t.LOAD_PERSONS, (state, action) => ({
  ...state,
  list: action.result
}))
);
export default personsReducer

And finally, in the ./store/reducers/helpers directory, we add helper functions for reducers. These help us keep our reducers more compact and readable, as you may have noticed in the persons reducer.

./store/reducers/helpers/index.js

export * from './load-reducer';
export * from './reduce-reducers';
export * from './reducers-map';

./store/reducers/helpers/load-reducer.js

const SUCCESS_MOD = '_SUCCESS';
const FAIL_MOD = '_FAIL';
const INITIAL = {
loaded   : false,
loading  : false,
error    : null,
errorCode: null
};
const defaultSuccessReducer = state => state;
export function loadReducer(actionType, successReducer = defaultSuccessReducer) {
const REQUEST = actionType;
const SUCCESS = actionType + SUCCESS_MOD;
const FAIL = actionType + FAIL_MOD;
return (state = INITIAL, action) => {
  switch (action.type) {
    case REQUEST:
      return {
        ...state,
        loading : true,
        error : null,
        errorCode: null
      };
    case SUCCESS:
      return successReducer({
        ...state,
        loading: false,
        loaded : true
      }, action);
    case FAIL:
      return {
        ...state,
        loading : false,
        error : action.error,
        errorCode: action.errorCode
      };
    default:
      return {
        ...INITIAL,
        ...state
      };
  }
 }
}

./store/reducers/helpers/reduce-reducers.js

export function reduceReducers(initialState, ...reducers) {
return (state = initialState, action) => {
  return reducers.reduce(
    (s, reducer) => reducer(s, action),
    state
  )
 }
}

./store/reducers/helpers/reducers-map.js

export function reducersMap(reducerMap) {
return (state, action) => {
  const reducer = reducerMap[action.type];
  return reducer
    ? reducer(state, action)
    : state
 }
}

Good, now let’s inject redux store into our app. For that, we need to modify our index.js file; when complete, the file will look like this:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from './store'
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={createStore()}>
  <App/>
</Provider>,
rootElement
)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Now, we can connect the store to any components we create and use any data that we need. So, let’s change the App.js file to display dummy persons:

import React, { Component } from 'react';
import { connect } from 'react-redux'
import { loadPersons, getPersons } from './store';
const mapStateToProps = state => {
const { loading, loaded, error, list: persons } = getPersons(state);
return {
  loading,
  loaded,
  error,
  persons
}
};
class App extends Component {
componentWillMount() {
  this.props.loadPersons()
}
renderContent() {
  const { loading, error, persons } = this.props;
  if (loading) {
    return (
      <div>
        Loading...
      </div>
    )
  }
  if (error) {
    return (
      <div className="alert alert-danger">
        Error: {error}
      </div>
    )
  }
  return (
    <ul className="list-group">
      {persons.map((person, index) => (
        <li key={index} className="list-group-item">
          <div>{person.name}</div>
          <div className="text-muted small">{person.address}</div>
        </li>
      ))}
    </ul>
  )
}
render() {
  const { loading, loaded, error, persons } = this.props;
  console.log({ loading, loaded, error, persons });
  return (
    <div className="container">
      <div className="header">
        <h3>
          Backendless React Addresses Book
        </h3>
      </div>
      {this.renderContent()}
    </div>
  );
}
}
export default connect(mapStateToProps, { loadPersons })(App);

Moment of truth! If you can see the same as me, we are synced! =)
Address book React web app in progress

Use Backendless Data

As you likely noticed, we have used mock data in the loadPersons action. Now it’s time to use real data from our Backendless Server. Let’s put our dummy persons list into Backendless. First, we need to create a new table on the Backendless -> Data screen:
Add new table in Backendless
Next, we are going to import all the dummy persons into the table. Backendless’ REST Console will help us with that:
Import data into a table in Backendless
// Request body

[
 { "name": "Tony Stark", "address": "10880 Malibu Point" },
 { "name": "Bruce Banner", "address": "2766 Taylor Street" },
 { "name": "Steve Rogers", "address": "New York City, Brooklyn" },
 { "name": "Thor Odinson", "address": "Palace in Asgard" }
]

Let’s check our work in the Data Browser:
Review imported data in table
As you can see, we didn’t do any additional steps to set up the schema for the Person table, but there are two columns generated automatically: “name” and “address”. That’s because we have enabled dynamic schema definition by default, so when we saved our dummy person objects in the REST Console, the server automatically created the two columns for us. It’s a great feature for development, but for production, we recommend switching off the option so your tables don’t get modified unintentionally.
Data Configuration - ReactArticle - Backendless
Alright, let’s add code for retrieving the persons objects within our app. We will use the Backendless JS-SDK. Let’s install it from npm with the following command:

npm i backendless -S

Initialize the Backendless app in our index.js file. To do so, just import the backendless module and call Backendless.initApp(APP_ID, API_KEY) :

import Backendless from 'backendless'
Backendless.initApp('057B4BBA-41FE-52E2-FF04-2ACE042DC700', 'D008C0D7-9985-BB61-FFEA-E48502047900');

You can find your App ID and API Key in the Backendless Console on the Dashboard page:
App ID and API Key in Backendless Console
Now go to ./store/actions/persons.js and replace the dummy request with a real one:

import Backendless from 'backendless'
import t from '../action-types';
export const loadPersons = () => ({
types  : [t.LOAD_PERSONS, t.LOAD_PERSONS_SUCCESS, t.LOAD_PERSONS_FAIL],
apiCall: () => Backendless.Data.of('Person').find(),
});

Did you catch it? I made a typo while writing Backendless.Data.of(‘Person’).find(): instead of “Person” I wrote “Persons”,  but since there is no table with the name “Persons”, we see an error in our browser where our app is open. Error handling works perfectly! That’s cool, isn’t it?
React web app error handling

Summary

This is where we will break for today. In this first part, we got a good foundation for our app, built basic app structure, setup application store, and now we know how to load data from Backendless and display it in our React app. In the coming articles in this series, we will add more functionality and we’ll use the real-time database functionality of Backendless.

Thank you for reading and see you soon!

Leave a Reply