Skip to Content
Technical Articles

Correct Routing with React + CAP + AppRouter + HTML5 Repo Service

Index

  1. App Init
  2. Installing React Modules
  3. Create Redux Store
  4. Integrate Redux Store
  5. Example React Component using connected react router
  6. Last Steps
  7. Conclusion

Hi everyone!

Do you want to build awesome apps leveraging the power of React and SAP Cloud Foundry? Well let’s get started in today’s topic: hash routing.

Today we are going to learn how to do hash routing with react + CAP + AppRouter + HTML5 Repo Service following some easy steps! Because you can have a website with a few buttons and pictures, but the minute the app starts growing you are going to need routing. I will share with you how I made routing possible with an example and please provide any feedback if you find it useful or if you want to suggest any improvements.

App Init

To start off we will be using a small script my team and I created to make your life easier when building full stack applications in CF called cf-create-app. This will install CAP with an approuter, react application, html5 repo service, add Hana and some tweaks so deployment is as straightforward as possible. Simply run

$ npx cf-create-app <Project Name>

It will take a few minutes, mainly due to react’s app installation.

In the generated folder you will find a CAP app with the usual folders plus new ones:

  • app/ containing the react application – it has a modified manifest plus an xs-app.json in the public folder to properly work with HTML5 Repo Service
  • srv/ all your service definitions – defaults have been created so app doesn’t crash on start
  • db/ all domain models and database
  • html5Deployer/ required to deploy html5 applications in html5 repo service
  • approuter/ approuter with default routes to backend and html5 application from HTML5 Repo service
  • mta.yaml pre-configured mta.yaml – will handle your deployment, don’t touch this unless you know what you are doing

After installation is complete you can simply go into your project folder and run these commands to get the application started. This will start the react app and execute cds watch to get your CAP app running:

$ cd <Project Name>
$ npm run dev

That’s it!

Installing React Modules

Next go into the app folder and we will install some dependencies. First thing we are going to install is connected-react-router and history. Personally I had an issue where installing history version 5.0.0 would cause the redux state not to update properly so if you run into that issue install version 4.10.1 like I did. Run the following commands in the app folder:

$ npm install history@4.10.1 connected-react-router

You may use a different module but this one will let us dispatch history methods (works with redux) and has support with React Router v4 and v5. Secondly install react router and the already mentioned redux, I suppose you already know this libraries.

$ npm install react-router react-router-dom redux react-redux

Create Redux Store

After that we need to create a reducer with the specific key router. Personally I like to keep things clean, you may want to create a folder called store inside the src folder with the following files.

$ mkdir store && cd store

Reducers
// PROJECT/app/src/store/reducers.js

import { combineReducers } from 'redux'
;
import { connectRouter } from 'connected-react-router';



export default (history) => combineReducers({
 
     router: connectRouter(history),
 
     ... // rest of your reducers

})


If you have worked with redux in the past these reducers file works as usual, just add as many reducers as you may need in your project. If you haven’t worked with redux in the past and enjoy working with react I highly encourage you to start reading about it as it will make your react life incredibly amazing. I particularly enjoy working with Redux Toolkit https://redux-toolkit.js.org/

Moving on, let’s create the store!

Create middleware
// PROJECT/app/src/store/configureStore.js

import { createHashHistory } from 'history';

import { applyMiddleware, compose, createStore } from 'redux';

import { routerMiddleware } from 'connected-react-router'
;
import createRootReducer from './reducers'

;

export const history = createHashHistory({
  
     hashType: 'slash',

     getUserConfirmation: (message, callback) => callback(window.confirm(message))

});




export default function configureStore(preloadedState) {
 
     const store = createStore(
  
          createRootReducer(history), // root reducer with router state
          preloadedState,
  
          compose(
   
              applyMiddleware(
    
                   routerMiddleware(history), // for dispatching history actions
    
                   // ... other middlewares ...
   
                   ),
  
               ),
 
          )

 
       return store

}

 

Integrate Redux Store

To make all of this configuration work we will add the configured store to our index.js in our src folder like the following. Take also a look at the route pointing to the wildcard ‘*’. We are using this to redirect to our home page to avoid broken routes.

Index.js
// PROJECT/app/src/index.js

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

import * as serviceWorker from './serviceWorker';


import { Provider } from 'react-redux';

import { Route, Switch } from 'react-router'; // react-router v4/v5

import { ConnectedRouter } from 'connected-react-router';

import configureStore, { history } from './store/store';

import { Redirect } from 'react-router-dom';


import DefaultComponent from './DefaultComponent';



const store = configureStore(/* provide initial state if any */);



ReactDOM.render(
 
     <Provider store={store}>
  {/* place ConnectedRouter under Provider */}
   
     <ConnectedRouter history={history}>
   
     <>
    {/* your usual react-router v4/v5 routing */}
    
          <Switch>
     
               <Route
 exact
 path='/index.html'
 render={() => (
                    <DefaultComponent name='Match' redirect='/miss/index.html' />
      
                  )}
     
               />

               <Route
 path='/miss/index.html'
 render={() => (

                    <DefaultComponent name='Miss' redirect='/index.html' />

                 )}
     
               />
     
               <Route path='*' render={() => <Redirect to='/index.html' />} />
    
           </Switch>
   
     </>
  
     </ConnectedRouter>
 
     </Provider>,
 
     document.getElementById('root')

);

serviceWorker.unregister();

 

Example React Component using connected react router

Furthermore, you will want to see it in action! So I put together a small component that will help you understand how to do the routing with this library.

DefaultComponent.js
// PROJECT/app/src/DefaultComponent.js

import React from 'react';

import { useDispatch } from 'react-redux';

import { push } from 'connected-react-router';



function DefaultComponent(props) {

     const dispatch = useDispatch();

 
  
     function handleRouting(r) {
  
          dispatch(push(r));
 
     }

 
  
     return (
  
          <div>
   
               <button onClick={() => handleRouting(props.redirect)}>
     
                    {props.name}
   
               </button>
  
          </div>
 
     );

}

export default DefaultComponent;

 

We have learnt how to implement basic hash routing, how to ‘push’ to a different route and how to redirect. You can try it locally by clicking the buttons ‘Match’ and ‘Miss’ of you react app – see how the route changes. Let’s move to the cloud!

Last Steps

With the library cf-create-app all modules are added and configured in your mta.yaml so you don’t have to do anything manually, unless you want to add your own services or tweak them by yourself. It has been configured so you only have to build and deploy 🤓

Run the following commands in your project’s folder:

$ mbt build
$ cf deploy mta_archives/<you generated filename>

Take into account, for this to work you will need to have enabled HTML5 Repo Service and have enough quota. Once your app has deployed go to the approuter URL provided from the cf cli and hopefully your app will be up and running!

Conclusion

Today we have learnt how to quickly create an application using CAP + approuter + react + html5 repo service. The react application will be deployed in the HTML5 Repo service and consumed by the approuter. In order to navigate through our app we’ve implemented hash routing which prevents our routes from breaking.

There is only one question remaining. Why do all my routes end with ‘/index.html’? I have not been able to figure this out yet, but when deploying apps with the HTML5 Repo Service and consuming them from the approuter, unless I provide the index.html at the end of the route, the approuter won’t be able to find the file. Please provide feedback if you know the answer!

If you have any questions please feel free to do so and you can also check the repo with this project.
https://github.com/Turutupa/cf-html5-repo-hash-routing-react

If you like cf-create-app please follow and support
https://github.com/turutupa/cf-create-app

Happy hacking! 🙂

Be the first to leave a comment
You must be Logged on to comment or reply to a post.