Existing projects or samples

Getting Started with Redux (by Dan Abramov)

  • The state of the application is a pure data object.

  • Changes in the application are done through Actions, which are just data as well. Contain a "type" field which is a string for serialization purposes.

  • In Redux, state mutations in the application are done through pure functions that take current state and an action, and produce the next state. This function is the Reducer.

  • When the action passed to a reducer is unknown (e.g. a typo in the string of the type), the state returned is the current one.

  • In Redux there is the convention that if the reducer receives an undefined state, then it will return the default state (e.g. 0 for a counter).

Store

The store:

  • Holds the current state (and returns it, store.getState()).

  • Allows to dispatch Actions (store.dispatch({"something": "here"})).

  • Is initialized with the Reducer.

  • Allows to subscribe callbacks when the state changes.

The way the createStore() function is implemented, holds the state of the application in the internal closure, but has a getState() function to get a copy of it to be read.

const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => { // Return a function that unsubscribes THIS listener
      listeners = listeners.filter(l => l !== listener);
    };
  };

  dispatch({}); // Initializes the state when the store is created.

  return { getState, dispatch, subscribe };
};

Building React Applications with Idiomatic Redux

This is the second course from Dan Abramov, and while there is a lot more React than previously, and less focus in Redux, I’ve found some interesting architecture details:

  • When the React Router is introduced, the visibility filter is moved out of the state of the Redux Store, and the value is read from the Router. It is explained that is fine that the whole application state is not exactly on the Store as long as there is a single source of truth for each part of the data. For Qt GUI app, this could be equivalent to which entry is selected on a combobox, or which tab/page is being shown, etc.

  • The application had (as of the start of 1071, "Colocating Selectors with Reducers") a mapStateToProps function that called getVisibleTodos and which did return the list of actual elements to be displayed according to the filter selected by the user. That chapter of the course rearranges the code and puts the function, which is called "selector", next to the todos reducer. The reason is that both should know about the structure of this same object specifically.

Concepts

Starting from the ones listed in basics in the Redux documentation.

Actions

"payloads of information that send data from your application to your store". Seem like commands in the command pattern. And are immutable values.

Action creators

Initially it just seemed like constructors to me because those return an action: function addTodo(text) { return { type: ADD_TODO, text } }. To initiate a dispatch, pass the returned action to dispatch(), or create an action creator bound to call the dispatch function (const peformAddTodo = text ⇒ dispatch(addTodo(text))). However, something that differs from constructors: "Action creators can also be asynchronous and have side-effects". So they are more like a factory function.

Managing in C++ the complexity of a huge state, reducer and notifier list

The core of dispatching actions is:

state = reducer(state, action);
listeners.forEach(listener => listener());

This is very simple, but it has a few problems in a large application, mainly about compilation times or organization that prevents conflicts when multiple developers touch the same interconnected pieces.

Actions in ReduxJS are JavaScript objects, so of the same type. In C++ we could use generic structures, like if it were a JSON object, but it would be better if actions were of many types (for safety), defined probably in different files.

We can wrap all the action types in std::variant, then use std::visit to make a visitor that just calls an appropriate reducer that only handles one specific action. So each one is an overload of the same function in the same namespace, but each uses only one or a few Action types, so we don’t need to include the world in each reducer implementation:

struct Reducer
{
    const State& current;
    template<typename SpecificAction>
    State operator()(const SpecificAction& action)
    {
        return AppReducers::reducer(current, action);
    }
};

Then the Store can do:

m_state = std::visit(Reducer{m_state}, action);

This only forces us to have a centralized place where we define the variant, and where we include all the reducers of the application (so that the root reducer, the template of operator()) can actually call all the overloads. The risk of conflicts can be lowered by using some grouping into files, but not the fact that the central place needs to be recompiled (unless type erasure is added in some group, which is less safe, but could work as an alternative in many large projects). That is not a big problem, and I think it is a solved issue: each reducer can be in a separate file, and doesn’t need to include the headers of all the events.

A bigger problem is that the state can end up being huge, as will be defined by aggregating other structures from different places, in nested ways. Everything has to be inline. But if the state is so global, and every reducer needs to have its definition, every time the layout of the state changes, all the reducers will need to be recompiled.

In ReduxJS this is not exactly a problem, I think, but it is still addressed by combineReducers. This works like this:

rootReducer = combineReducers({foo: fooReducer, bar: barReducer})
// This would produce the following state object:
foo: { // Data related to "foo" and produced by fooReducer
},
bar: { // Data related to "bar" and produced by barReducer
}

Producing side effects

The approach of Lager, is to make the reducer functions return both a state and a side effect (which is often lager::noop). This IMHO uglifies a lot the reducers because of the more complex return type, and even though the lambda functions are values, so everything is still testable (and Lager provides some mechanism to inject dependencies that I still don’t fully understand), it feels a bit wrong to me.

A simple (maybe naive) idea, would be to add to the state of the application a sort of "task queue". Then an "imperative shell" could be a watcher of the state changes, and look at the changes in the queue. If the queue has some task pending to be performed, it performs it, and posts results to the store, so the reducers can cleanup the queue, and adjust state.

Example idea. A part of the application wants to tell the business logic that the an image has to be set (e.g. a background image, a profile picture, etc.). The image needs to be validated, however, so some part of imperative code with side effects needs to check if the image has the right resolution, aspect ratio, if it’s not corrupt, or whatever other detail.

I think there could be two approaches to solve this:

  1. The "everything goes through the store/reducers" approach.

    • The reducers first would see an event saying "an image with this file path wants to be set".

    • Which then add a task to the queue saying "I demand this image with this path to be validated".

    • The imperative/side-effects shell would see the task, and would run code to verify it, posting with a new event the results.

    • The store/reducers would accept or reject the image accordingly, setting a new state.

  2. The "functional core, imperative shell" approach. This would resemble a bit more what Gary Bernhadt showed in his talk. This would have an additional functional core making decisions, and the outer imperative shell would provide the dependencies.

Approach 1 seems to be a bit complicated, but maybe has the advantage that you can store in the state all the details, so if an application crashes or gets interrupted by OS or a power failure, it can resume from where it left.

Approach 2 gives you a lot more freedom, and allows a more traditional approach. It also gives you more opportunity to shoot yourself on the foot.

A third approach would probably be to use something like redux-thunk. That seems to basically be that an action creator posts a thunk to the store. A thunk would be something that gets passed the store, and is able to call it to post more actions when the side effects are successful or fail. Maybe it’s worth exploring it, but I don’t see much the point of it by itself. Maybe it makes some sense when there is an actual middleware chain that can interrupt the delivery?

Misc