{{announcement.body}}
{{announcement.title}}

How to Use Backendless With React.js

DZone 's Guide to

How to Use Backendless With React.js

In this post, we look at how to use this interestring Backend as a Service Platform along with React.js to create applications.

· Web Dev Zone ·
Free Resource

React.js is one of the best and most popular front-end frameworks available for app builders. The barriers to entry in terms of understanding how to develop an app with React are very low, which is why many JavaScript developers choose the React 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 the React-Redux store, show you how to deploy your app to Backendless File Storage, and demonstrate how to easily inject the into your React application.

React.js development

In this article, 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

Before we get going let's make sure you have a Backendless account. If you don't have on already, just register for new free account at https://develop.backendless.com.

Now let's create a new Backendless App. I will call my app "ReactArticle," but you can choose any name you wish.

Create Your React App

As an entry point for creating a new app we will use this post from the official React.js 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:

That's it! Pretty 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 code:

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 the future it will be more complex, so it's time to add 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 the changes in 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 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 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:

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 of the actions/selectors and createStore function.

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

./store/create-store.js

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

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 a mirrorKeys function. With the mirrorKeys function, we don't need to duplicate the action type value as 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 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, 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 file 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 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 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!

Use Backendless Data

As you likely noticed, we have used mock data in the 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:

Next we are going to import all the dummy persons into the table. Backendless's REST Console will help us with that:

// 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:

As you can see, we didn't perform any additional steps to setup schema for the Person table, but there are two columns, "name" and "address," automatically. 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.

Alright, let's add code for retrieving the persons objects within our app. We will use the Backendless JS-SDK so 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:

Now go to ./store/actions/persons.jsand 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 name "Persons" we see an error in our browser where our app is open. Error handling works perfectly! That's cool, isn't it?

Summary

This is where we will break for today. In this first part, we got a good foundation for our app, built the 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!



If you enjoyed this article and want to learn more about React, check out this collection of tutorials and articles on all things React.

Topics:
web app ,react.js tutorial ,javascript tutorial ,web application development ,application state

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}