remorse - the overall application structure

Before you look at remorse, you should consider learning about the basic building blocks of a redux application:

In the context of remorse we have trivial actions, e.g.

forceStopTraining() {
  return {

or complex ones (see e.g. evaluateUserInput in Training/TrainingActions.js).

Let’s have a look at the overall structure of remorse:

dir tree remorse

Generally speaking, code is separated by feature:

One of the decisions early on was to decide whether the reading and writing screen would be different or just the same code base. The pros / cons are obvious.

Since the two screens are indeed very similar at this point in time, I decided for the latter, hence the Training folder with sub folders.

This means that some items require specifics, which are bundled in the Reading/Writing specifics files. Let’s have a look at one:

import createTrainingActions from "../TrainingActions";
const Actions = createTrainingActions({
  trainDataStorageKey: APP_READ_TRAINDATA_KEY,
  expectedInputFromCurrentTrainingSet: morseCodeToChar
export default Actions;

createTrainingActions then creates a closure and the actions it returns become bound to the training specifics, e.g.

export default function createTrainingActions({
}) {
  return {
    saveTraining() {
      storeObject(trainDataStorageKey, trainingData);

The training view can now get training specific actions like such:

import TrainingScreen from "../TrainingScreen";
import Actions from "./TrainingSpecifics";

const WriteTraining = connect(function(s) {
  return s.train.currentTraining || {};
}, Actions)(TrainingScreen);

The features are tied back together in the AppReducer:

import { combineReducers } from "redux";
import train from "./Training/TrainingReducer";
import typing from "./TypingScreen/TypingReducer";

const appReducer = combineReducers({ typing, train });
export default appReducer;

Notice that there is no reducer for displaying the training data. This is because the training data screen runs independently of the redux infrastructure. This is because it is (sort of) legitimate to have the routing run independently of the redux application itself. Of course it depends on what you want to be able to do, but for this application there were no major drawbacks in doing so. So, since looking at training data is purely a readonly screen and any changes are just navigational changes, it runs without connected components (connected here means components wired up to the redux infrastructure).

And finally, App.js ties the different features together:

<BrowserRouter basename={process.env.REACT_APP_BASENAME}>
    <Provider store={store}>
        <div id="root">
        <Navigation />
        <Route path="/typing" component={TypingScreen} />
        <Route path="/test-writing-morse" component={WritingScreen} />
        <Route path="/test-reading-morse" component={ReadingScreen} />
        <Route path="/training-data" component={TrainingDataScreen} />