Skip to content

Main Frontend Tecnologies

The main tecnologies used on the mapstore 2 are:

  • ReactJS (View)
  • Redux (state management)

ReactJS

ReactJS 0.16.x is used to develop MapStore. The main purpose of ReactJS is to allow writing the View of the application, through the composition of small components, in a declarative way.

Components are written using a "templating" language, called JSX, that is a sort of composition of HTML and Javascript code. The difference between JSX and older approaches like JSP is that JSX templates are mixed with Javascript code inside javascript files.

ReactJS component example

Component definition:

1
2
3
4
5
class MyComponent extends React.Component {
   render() {
       return <h1>{this.props.title}</h1>;
   }
}

Component usage:

1
React.render(<MyComponent title="My title"/>, document.body);

Properties, State and Event handlers

Components can define and use properties, like the title one used in the example. These are immutable, and cannot be changed by component's code.

Components can also use state that can change. When the state changes, the component is updated (re-rendered) automatically.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class MyComponent extends React.Component {
   state = {
      return {
         title: 'CHANGE_ME'
      };
   };
   changeTitle = () => {
      this.setState({
         title: 'CHANGED'
      });
   };
   render() {
       return <h1 onClick={this.changeTitle}>{this.state.title}</h1>;
   }
}

In this example, the initial state includes a title property whose value is CHANGE_ME.

When the h1 element is clicked, the state is changed so that title becomes CHANGED.

The HTML page is automatically updated by ReactJS, each time the state changes (each time this.setState is called). For this reason we say that JSX allows to declaratively describe the View for each possible application state.

Lifecycle hooks

Components can re-define some lifecycle methods, to execute actions in certain moments of the component life. Lifecycle API is changed in react 16 so please refer to the official documentation.

Redux

Redux, and its companion react-redux are used to handle the application state and bind it to ReactJS components.

Redux promotes a unidirectional dataflow (inspired by the Flux architecture) and immutable state transformation using reducers, to achieve predictable and reproducable application behaviour.

A single, global, Store is delegated to contain all the application state.

The state can be changed dispatching Actions to the store.

Each action produces a new state (the state is never changed, a new state is produced and that is the new application state), through the usage of one or more reducers.

(Smart) Components can be connected to the store and be notified when the state changes, so that views are automatically updated.

Actions

In Redux, actions are actions descriptors, generated by an action creator. Actions descriptors are usually defined by an action type and a set of parameters that specify the action payload.

1
2
3
4
5
6
7
8
9
const CHANGE_TITLE= 'CHANGE_TITLE';

// action creator
function changeTitle(newTitle) {
    return {
        type: CHANGE_TITLE,
        title: newTitle
    };
}

Reducers

Reducers are functions that receive an action and the current state and:

  • produce a new state, for each recognized action
  • produce the current state for unrecognized actions
  • produce initial state, if the current state is undefined
1
2
3
4
5
6
7
8
function reducer(state = {title: "CHANGE_ME"}, action) {
    switch (action.type) {
        case CHANGE_TITLE:
            return {title: action.title};
        default:
            return state;
    }
}

Store

The redux store combines different reducers to produce a global state, with a slice for each used reducer.

1
2
3
4
5
6
7
var rootReducer = combineReducers({
   slice1: reducer1,
   slice2: reducer2
});
var initialState = {slice1: {}, slice2: {}};

var store = createStore(rootReducer, initialState);

The Redux store receives actions, through a dispatch method, and creates a new application state, using the configured reducers.

1
store.dispatch(changeTitle('New title'));

You can subscribe to the store, to be notified whenever the state changes.

1
store.subscribe(function handleChange() {});

Redux Middlewares

Redux data flow is synchronous. To provide asynchronous functionalities (e.g. Ajax) redux needs a middleware. Actually MapStore uses 2 middlewares for this purpose:

  • Redux thunk (going to be fully replaced by redux-observable)
  • Redux Observable

Redux thunk

This middleware allows to perform simple asynchronous flows by returning a function from the action creator (instead of a action object).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// action creator
function changeTitleAsync() {
    return (dispatch, getState) => {
        myAsyncPromise.then( (newTitle) => {
            dispatch({
                type: CHANGE_TITLE,
                title: newTitle
            };)
        });
    }
}

This middleware is there from the beginning of the MapStore history. During the years, some better middlewares have been developed for this purpose. We want to replace it in the future with redux-observable.

Redux Observable and epics

This middleware provides support for side-effects in MapStore using rxjs. The core object of this middleware is the epic

1
function (action$: Observable<Action>, store: Store): Observable<Action>;

The epic is a function that simply gets as first parameter an Observable (stream) emitting the actions emitted by redux. It returns another Observable (stream) that emits actions that will be forwarded to redux too.

So there are 2 streams:

  • Actions in
  • Actions out

A simple epic example can be the following:

1
2
3
const pingEpic = action$ =>
  action$.filter(action => action.type === 'PING')
    .mapTo({ type: 'PONG' });

Every time a 'PING' action is emitted, the epic will emit also the 'PONG' action.

See :

Redux and ReactJS integration

The react-redux library can be used to connect the Redux application state to ReactJS components.

This can be done in the following way:

  • wrap the ReactJS root component with the react-redux Provider component, to bind the Redux store to the ReactJS view
1
2
3
4
5
6
React.render(
    <Provider store={store}>
        {() => <App />}
    </Provider>,
    document.getElementById('container')
);
  • explicitly connect one or more (smart) components to a all or part of the state (you can also transform the state and have computed properties)
1
2
3
4
5
6
connect(function(state) {
    return {
        title: state.title,
        name: state.namespace + '.' + state.name,
    };
})(App);

The connected component will get automatic access to the configured slice through properties:

1
2
3
function render() {
   return <div><h1>{this.props.title}</h1><p>{this.props.name}</p></div);
}