Let's take a look at the generated code. Since we already saw the server-side code in previous chapters, we will only look at the client-side code here:

The structure is quite similar to what we saw for Angular, but the React code is organized slightly differently. We are concerned only about the code inside src/main/webapp/app as everything else is exactly the same as what we saw for the Angular application.
Let's take a look at some of the important parts of the code:
- index.tsx: This is the entry point of our application. This is where we bootstrap React to the root div and initialize the Redux store:
...
const devTools = process.env.NODE_ENV === 'development' ? <DevTools /> : null;
const store = initStore();
registerLocales(store);
const actions = bindActionCreators({ clearAuthentication }, store.dispatch);
setupAxiosInterceptors(
() => actions.clearAuthentication('login.error.unauthorized')
);
const rootEl = document.getElementById('root');
const render = Component =>
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<div>
...
{devTools}
<Component />
</div>
</Provider>
</AppContainer>,
rootEl
);
render(AppComponent);
...
- app.tsx: This is our main application component. We declare the React router and the main application UI structure here:
...
export class App extends React.Component<IAppProps> {
componentDidMount() {
this.props.getSession();
}
handleLogout = () => {
this.props.logout();
};
render() {
const paddingTop = '60px';
return (
<Router>
<div className="app-container" style={{ paddingTop }}>
<Header
...
/>
<div className="container-fluid view-container" id="app-view-container">
<Card className="jh-card">
<AppRoutes />
</Card>
<Footer />
</div>
<ModalContainer />
</div>
</Router>
);
}
}
...
- routes.tsx: The application's main parent routes are defined here and they are imported in the app.tsx from here.
- config: This is where framework level configurations are done:
- axios-interceptor.ts: HTTP interceptors are configured here. This is where the JWT tokens are set to requests and errors are handled.
- constants.ts: Application constants.
- *-middleware.ts: Error, Notification, and Logging middleware for Redux are configured here.
- store.ts: Redux store configuration is done here. Middlewares are registered during this stage.
The order of the middlewares in the array is important as they act like a pipeline, passing actions from one middleware to another as shown here:
const defaultMiddlewares = [
thunkMiddleware,
errorMiddleware,
notificationMiddleware,
promiseMiddleware(),
loadingBarMiddleware(),
loggerMiddleware
];
-
- translation.ts: i18n-related configurations are done here.
- entities: The entity modules are present here.
- modules: Application UI modules are here:
- account: Account pages like settings, password reset, and so on are here
- administration: The admin screens like metric, health, user-management, and so on are here
- home: Home screen of the application
- login: Login and logout components
- shared: Shared components and reducers:
- layout: Layout related components like header, footer, and so on
- model: Typescript model for entities
- reducers: shared reducers used by the application:
- authentication.ts: This is for authentication-related actions and reducers. Let's use the LOGIN action. The action accepts username, password, and rememberMe and dispatches the ACTION_TYPES.LOGIN with an asynchronous payload from an HTTP call to authenticate our credentials. We use the async/await feature from ES7 to avoid complex callbacks here. The result from the dispatch is obtained from when we extract the JWT bearerToken and store it in the local or session storage of the browser based on the remember me setting passed. The dispatch of ACTION_TYPES.LOGIN will trigger the appropriate case in the reducer based on the status of the promise:
...
export const ACTION_TYPES = {
LOGIN: 'authentication/LOGIN',
...
};
const initialState = {
...
};
// Reducer
export default (state = initialState, action) => {
switch (action.type) {
case REQUEST(ACTION_TYPES.LOGIN):
case REQUEST(ACTION_TYPES.GET_SESSION):
return {
...state,
loading: true
};
case FAILURE(ACTION_TYPES.LOGIN):
return {
...initialState,
errorMessage: action.payload,
showModalLogin: true,
loginError: true
};
...
case SUCCESS(ACTION_TYPES.LOGIN):
return {
...state,
loading: false,
loginError: false,
showModalLogin: false,
loginSuccess: true
};
...
default:
return state;
}
};
...
export const login =
(username, password, rememberMe = false) => async (dispatch, getState) => {
const result = await dispatch({
type: ACTION_TYPES.LOGIN,
payload: axios.post('/api/authenticate', { username, password, rememberMe })
});
const bearerToken = result.value.headers.authorization;
if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') {
const jwt = bearerToken.slice(7, bearerToken.length);
if (rememberMe) {
Storage.local.set('jhi-authenticationToken', jwt);
} else {
Storage.session.set('jhi-authenticationToken', jwt);
}
}
dispatch(getSession());
};
...
-
- util: Utility functions.
The folder structure of the unit test code is also quite similar:
