3

I'm trying to figure out why is this not working.

I'm using React with Redux on Typescript.

I have the following code:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {Action} from 'redux';

/** Redux Action **/
class AddTodo implements Action {
   type: string;
   id: number;
   text: string;
   constructor(id: number, text: string) {
       this.type = 'ADD_TODO';
       this.id = id;
       this.text = text;
   }
}

// ...

class TodoApp extends React.Component {
   public render(): JSX.Element {
      return (
          <div>
              <button onClick={() => {
                 store.dispatch(new AddTodo(0, 'Test'));
              }}>
                 Add Todo
              </button>
          </div>
      );
   }
}

// ...

When I click on the button, I'm getting the following error on console:

Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.

I don't understand why is Redux assuming this is an async action or a complex object.

My reducers are not even being executed. They look like this:

const todoReducer: Reducer<Todo> = (state: Todo, action: Action): Todo => {
   if (action instanceof AddTodo)
      return { id: action.id, text: action.text, completed: false } as Todo;
   // ...
   else
      return state;
};

const todosReducer: Reducer<Todo[]> = (state: Todo[] = [], action: Action): Todo[] => {
   if (action instanceof AddTodo)
       return [...state, todoReducer(undefined, action)];
   // ...
   else
       return state;
}

If I pass in { type: 'ADD_TODO', id: 0, text: 'Test' } instead of new Action(0, 'Test'), then the error is gone, but my action is not correctly executed because of the conditions on the reducer.

Do you know what is going on here?

Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
  • could you please provide the snippet of your AddToDo? – Stelios Voskos Dec 23 '16 at 14:13
  • You have created an "object constructor", not a _plain_ object as the error message states. http://stackoverflow.com/questions/5876332/how-can-i-differentiate-between-an-object-literal-other-javascript-objects – ctrlplusb Dec 23 '16 at 14:19
  • @SteliosVoskos I have already provided it. It is the class at the top of the above code. If you mean the add logic, it is on the reducers I posted on the bottom of the post. – Matias Cicero Dec 23 '16 at 14:19
  • Here is what babel transpiles that class code into: https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=latest%2Creact%2Cstage-3&experimental=false&loose=false&spec=false&code=class%20AddTodo%20implements%20Action%20%7B%0A%20%20%20type%3A%20string%3B%0A%20%20%20id%3A%20number%3B%0A%20%20%20text%3A%20string%3B%0A%20%20%20constructor(id%3A%20number%2C%20text%3A%20string)%20%7B%0A%20%20%20%20%20%20%20this.type%20%3D%20'ADD_TODO'%3B%0A%20%20%20%20%20%20%20this.id%20%3D%20id%3B%0A%20%20%20%20%20%20%20this.text%20%3D%20text%3B%0A%20%20%20%7D%0A%7D – ctrlplusb Dec 23 '16 at 14:19
  • 1
    @ctrlplusb I'm using TypeScript, not babel. And the generated JS code from TS does not contain any `_classCallCheck` method. – Matias Cicero Dec 23 '16 at 14:21
  • My bad, but would imagine it's similar. Strange case yes. My guess? The preconditional check isn't covering all paths? – ctrlplusb Dec 23 '16 at 14:22
  • 1
    @ctrlplusb I have a `return state` as a default case. In any way, if the action is not recognized, it shouldn't throw this kind of error. – Matias Cicero Dec 23 '16 at 14:23
  • What are you getting when you import Action from redux? – BravoZulu Dec 23 '16 at 14:24
  • @BravoZulu I'm getting the type definition for `Action`: `export interface Action { type: any; }` – Matias Cicero Dec 23 '16 at 14:26

1 Answers1

3

This is explained by Dan Abramov himself:

https://github.com/reactjs/redux/issues/992

Basically, serialization and deserialization of actions (record / replay functionality) will not work if using my approach, because type information is not persisted.

Quoting Dan:

In other words, this is an anti-pattern:

function reducer(state, action) {
   if (action instanceof SomeAction) {
     return ...
   } else {
     return ...
   }
}

If we make it possible for TypeScript users, I know JavaScript users will be tempted and will abuse this because many people coming from traditional OO languages are used to writing code this way. Then they will lose benefits of record/replay in Redux.

One of the points of Redux is to enforce some restrictions that we find important, and that, if not enforced, don't allow for rich tooling later on. If we let people use classes for actions, they will do so, and implementing something like redux-recorder will be impossible because there's no guarantee people use plain object actions. Especially if boilerplate projects suddenly decide to adopt classes because “wow, it’s allowed now, it must be a great idea”.

Matias Cicero
  • 25,439
  • 13
  • 82
  • 154