React-based user interface for dime-server
- install node.js https://nodejs.org/en/, this will also install the NPM package manager.
npm install
to fetch NPM dependencies.npm start
and open http://localhost:3000 in your browser.- Happy coding.
The architecture of dime-ui is based on a boilerplate react-boilerplate. The introduction of react-boilerplate is a good read if you don't have previous knowledge on ReactJS + Redux project.
There are many well-written articles on ReactJS and Redux. I will focus on explaining how to create an new React component <VersionNumber />
in our UI.
As its name suggested, <VersionNumber />
is a HTML text node (<span>v1.2</span>
) showing the version number of dime-server. <VersionNumber />
is composed by a pair of HTML tags (<span></span>
) and the text(a string v1.2
) showing the respond Data by an API call to dime-server.
This is a common pattern for any UI component in our dime-ui project. The visible UI element represents an application state. The application needs to know where and how to get data(often done by an API call) to mutate the application state. This scenario should apply to any further UI developments in our project.
init a component -> compoent call API -> use respond data to mutates application state -> visible UI changes accoriding to the (mutated) application state
Create a React Component (see source code in this commit)
- Go to the root of the project, type
npm run generate
and:- Select
component
in the terminal.
- Select
- Select
ES6 Class
. 3. Name the componentVersionNumer
. 4. 'Does it have stying?' ->Y
. 5. 'Do you want i18n messages?'N
. (no need)
This will run the scripts that generate a React component. The new component will appear in ``./app/components/VersionNumber``. In this folder, there should have two files ``index.js``, ``style.css`` and a ``tests`` sub-folder.
- Now we have the
<VersionNumer />
component but we want to put it in the<NavBar />
, a horizontal navigation bar place on the top of the page. Go to./app/components/VersionNumber
and edit theindex.js
. - Please add
import VersionNumber from 'components/VersionNumber';
at the beginning of the file and add<VersionNumber value={this.props.value} />
in therender()
method ofNavBar
component. - Edit
app/containers/App/index.js
, addvalue={this.props.value}
on<NavBar />
in therender()
method. - To play safe, we should remind React to do type-checking by
value: React.PropTypes.number
in both two components and theApp
container. - Go back to
VersionNumber
component and replace therender()
with following code:
<div className={styles.value}>
<span>{this.props.value}</span>
</div>
Note there is a {this.props.value}
in the <span>
tag. If you are familiar with JSX syntax , you will know the {this.props.value}
should render the value of this.props.value
.
Now, we have a component <VersionNumber />
and it has been included in anther upper-level component <NavBar />
. The <App />
container then includes <NavBar />
. The value of version will be passed (as props
) from <App />
to <NavBar />
and then to <VersionNumber />
.
You may wonder what is the difference between components and containers.
Connect React-Redux (see source code in this commit)
- The next thing we will do is to 'connect' the application state in Redux store and pass it to the PropTypes of
<App />
container. By doing so, when the state in Redux store has been changed, the 'connection' will pass the new value to PropTypes and, the UI will then changes accordingly. - Go to
app/containers/App/index.js
, at the end of file, you will see a constantmapStateToProps
, add an new lineversionNumber: selectVersionNumber()
. TheselectVersionNumber()
is a 'selector' function of Redux state wrapped by a selector library for Redux. Basically, the 'selector' will substrate the data you want from Redux store. We have to create a new selector for current state-binding. - Go to
app/containers/App/select.js
and addconst selectVersionNumber = () => state => state.getIn(['app', 'versionNumber']).toJS();
. Don't forget to export the selector and add it back toapp/containers/App/index.js
. - Let's review the (unidirectional) data flow of our React application. It seems that the value of version number is stored in Redux. We create a selector to get a sub-state
app > versionNumber
from the redux store. The sub-state then connect/bind to the PropTypes of<App />
. The PropTypes then will be pass from from<App />
container to<NavBar />
component and then to<VersionNumber />
compoment.
Design UI Business Logic (see source code in this commit)
- The next thing we want to do is to modify the state that is stored as
app > versionNumber
. In Redux architecture, we need to dispatch an 'action' describing what happened and create a reducer function computing the 'next' state. However, in real-world application, the business logic of UI is complicated. In our case, the logic is - Fire a HTTP GET request.
- Receive responded data, check if there is error, handle the error.
- If no error, dispatch an Redux action.
- Reducer function received the action, compute the 'next' Redux state.
- Redux State change initials the UI change in step 11. There are many discussions on how to manage business logic in React-Redux application. We choose redux-saga.
- In our application, the way we get the version number is done by an API call (in this case is a GET HTTP request) to dime-server's endpoint
'/api/ping'. The question is when should we fire the HTTP GET request? From our understanding of React Component Life-cycle, the GET should be called immediately before compoment mounting occurs. That is, the
compomentWillMount()
. - In order to do so, we design a Redux action
getVersionNumber()
inapp/containers/App/actions.js
with an action-type calledGET_VERSION_NUMBER
. Import the action inapp/containers/App/index.js
, bind the action with the help ofmapDispatchToProps()
and pass the action via PropTypes from<App />
container to<NavBar />
compoment and then to<VersionNumber />
compoment. (Don't forget to add type checking ofthis.props.getVersionNumber
in components.) - The
getVersionNumber()
will fire right before the mounting of<VersionNumber />
. To actually call the GET request, we add a new saga functiongetVersionNumberWatcher()
inapp/containers/App/sagas.js
. Note that the sagas are written as ES6 generator (Here is the redux-saga tutorial) - The business logic in our saga tells redux-saga to call
getVersionNumberSuccess()
if no there is no error. ThegetVersionNumberSuccess()
is a Redux action that will dispatch an object with a typeGET_VERSION_NUMBER_SUCCESS
and the value of version number. - It is time to write the reducer for version number, add a new case in
app/containers/App/reducer.js
with the following code:
case GET_VERSION_NUMBER_SUCCESS:
return state
.setIn(['veriosnNumber'], action.version);
Note that we are using Immutable to process the changes of Redux state. See the full API of Immutable.
Let's review the business logic describe in step one and their the implementations.
- Fire a HTTP GET request. (implemented in
compomentWillMount()
of<VersionNumber />
) - Receive responded data, check if there is error, handle the error. (implemented as
getVersionNumberWatcher()
saga inapp/containers/App/sagas.js
) - If no error, dispatch an Redux action. (implemented as
getVersionNumberWatcher()
saga inapp/containers/App/sagas.js
) - Reducer function received the action, compute the 'next' Redux state. (implemented as a reducer-case in
app/containers/App/reducer.js
) - Redux State change initials the UI change in step 11.