Redux + DDP , no minimongo

Here’s an example app which demonstrates using Redux without Minimongo :


http://reduxddp.meteor.com/

Originally started as a fork of @SkinnyGeek1010 redux example. It implements the idea behind this quote from @mattkrick :

When I thought about it, redux is a state store, so everything that’s in minimongo should be in redux.

Everything that would normally be in Minimongo goes into the Redux store in this example.

I think that Minimongo and the Redux Store are conceptually extremely similar, so it’s really hard to decide which should have ownership of client state management. We can still use pub/sub and meteor methods, and all the great things about DDP without Minimongo. Right now, this is just an idea with the basic proof of concept example, I wonder what people think about it who have more experience with Redux @abhiaiyer @arunoda @ffxsam ?

9 Likes

This is really nice! I like this approach a lot more than the flux helpers i’m using. :thumbsup:

Nice. What about removal of documents with actions?

Nice! Now I can get rid of the tracker hack and minimongo duplication on the client and go directly to the source of truth (DDP), thanks for sharing! :smile:

Nice :smile:
But I don’t understand what is going on in your DDP.js file.
How can you import DDP.js from DDP.js ?

Great stuff! Really nice intermediary step to stay with Meteor pubsubs & opening up the possibility of piping data through redux or any other 3rd party store.

1 thing I might suggest is that your optimistic UI needs some work.
For example, you’ll get a fail if you had something like UPDATE_SCORE, DELETE_PLAYER, UPDATE_SCORE_FAIL.
The correct way to do it is to save the state just before a request is made & all the following actions after it. When a fail comes in, go back in time to just before the request & repeat all the saved actions except the request.

You might find this useful: https://github.com/mattkrick/redux-optimistic-ui :wink:

2 Likes

@ciwolsey To remove a document,

// call a method when the user clicks a button
    Actions.deletePlayer = function deletePlayer(_id) {
      Meteor.call('players.delete', _id);
      return {
        type: 'DELETE_PLAYER',
        playerId: _id
      };
    };

// the 'removed' message will be sent to the client via DDP
   DDP.on('removed', function (message) {
      if(message.collection !== 'players'){return}
      Store.dispatch(Actions.playerDeleted(message.id));
    });


  //  an action is dispatched to notify the store after a player has been deleted.
    Actions.playerDeleted = function playerDeleted(_id) {
      return { type: 'PLAYER_DELETED', player: {_id: _id}};
    };

// the state is updated in the reducer
        case 'PLAYER_DELETED':
          return _.omit(state, action.player._id);

@benoit The package name happens to ‘ddp.js’ , I agree that is totally confusing, I tried to clear it up in the comments https://github.com/looshi/redux-ddp/blob/master/client/DDP.js#L2

So I suppose you use Meteor 1.3 and install DDP.js with npm, right?

@benoit, yes, that’s correct. Using Meteor 1.3 and installing many packages via npm.

1 Like

@mattkrick, yes you are correct, the optimistic UI implementation is currently over-simplified and problematic. Thanks for sharing your library! It seems like a viable way to do optimistic UI alongside Redux.

Shouldn’t the method calls be done with redux-thunk?

export function deletePlayer = playerId => dispatch => {
  // The optimistic UI thingy
  dispatch({ type: 'DELETE_PLAYER', playerId });
  Meteor.call('players.delete', playerId, error => {
    /* dispatch other stuff here if you want*/
    // or take back update if error happened
  });
};

I was wondering whether it would be worth it to implement something that would use Meteor.connection's registerStore to sync collection subscription data straight into a Redux store without touching minimongo.

And for those who don’t know, the Meteor.connection.registerStore is what Mongo.Collection uses under the hood to feed minimongo and is also used by packages that integrate other third-party databases like ccorcos:any-db, numtel:mysql.

In addition, this would do away with having to use that ddp.js NPM package and we can just depend on the actual Meteor connection lol. Would you guys be interested in that?

5 Likes

Great! In the example app when I select a player and hit the points button quickly 3 times, it updates the UI with 15 points, but later when server updates come back it updates slowly only 2 times. Is that an intentional slow-response-with-bug?

No time to check the source, but I like the idea. Even though @SkinnyGeek1010 flux-helpers worked flawless so far (and faster) imho.

@lai: definitely. Less dependencies is better because who knows who is maintaining that package, when we know MDG will keep core updated.

@lai thanks for pointing out we can use the redux-thunk package, @sikanx noticed that as well, we have an issue here : https://github.com/looshi/redux-ddp/issues/2 . To be totally honest, I don’t know whether or not it should be used. Would you describe it as a good way to escape callback hell ?

Also, to your point on using Meteor.connection.registerStore . That’s great, I was actually looking around for where collection data is stored on the client when you subscribe, but before you create the collection. I would like to see what it looks like to interface with this registerStore, does it have a friendly API ? Maybe you can fork the example and try it out.

Using Redux middleware is the standard practice in the Redux world if you want to cause side-effects. I am simply echoing Dan Abramov in his guide. A standard action creator should not cause any side effects. If you want side-effects (aka asynchronous actions), use redux-thunk or redux-promise; redux-thunk is just 8 lines of code.

You’ll also notice that there are usually three standard actions you dispatch in an async action creator. One to indicate you’re fetching (loading…), one to feed the results (success) or one to feed the error (fail).

Also, I think the most important reason to use redux-thunk is that it allows you to access the entire state. Chances are, you’re going to need something in the state that you don’t immediately have access to when you call the action.

export function asyncComplexAction = () => (dispatch, getState) => {
  const { somethingElse, andThis } = getState();
  Meteor.call('something.using', somethingElse, andThis, error => {
    dispatch({ type: 'OK' });
  });
};

To somewhat escape callback hell, you can implement a promise-based redux-thunk like:

export function deletePlayer = playerId => dispatch => new Promise((resolve, reject) => {
  Meteor.call('players.delete', playerId, error => {
    if (error) {
      reject(error);
    } else {
      resolve();
    }
  });
});

This allows you to chain your dispatches like:

dispatch(deletePlayer('1'))
  .then(/* dispatch something else?... */)
  .then(/* etc... */);

See Dan Abramov’s example code.

The reason for you should use promises is because you’ll eventually be able to take advantage of async and await (they will be available in Meteor soon I think), which will completely get rid of callback hell while preserving asynchronous execution.

There’s not a lot of documentation on the Meteor.connection.registerStore API really. I would need to read the code and its comments, which I started doing. I’ll play around and see what I come up with.

1 Like

I think this is ideal but when I was talking to Dan he told me that having side effects in the action creator is ok. Thunks do decouple but too much decoupling can lead to total loss of cohesion which can be a worse problem (totally depends on the app/size though).

IMHO no matter how you slice it firing said action creator will cause a side effect somewhere, even if it’s not directly.

That being said I do think middelwares are something to look into once you’re comfy with Redux… just not necessary at first. Nice examples btw! :thumbsup:

1 Like

@lai thanks for sharing! I’m still not sure that calling an API would create a real ‘side-effect’ because it doesn’t actually change anything at all by itself.
I like that Redux promotes these concepts :
https://en.wikipedia.org/wiki/Law_of_Demeter



And it’s great you can just use Redux and be doing “Dependency Injection” naturally. Perhaps that’s what all the functional programming newfound interest derives from ?

yes, the biggest advantage of function programming is that everything is … in functions;

So everything is composable and replaceable by any thing (given loosely coupled good code);

You see concepts like this being used in middleswares in redux, express, and even ruby;

The fact that you can just plug a function in the middle of your function chain and have it just work is amazing.

In react, when you have a <tag> inside another <childtag>, you are basically just wrapping a function inside a function, so it makes it easy to have composable components.

Since everything is just a transformating of data, you just run that data through a chain of functions and it should just work. When run state thru a chain of functions, it returns HTML, and when you run a request through a chain of functions it returns a response

In these cases functional programming provides a lot of business value

2 Likes

@looshi, so I just forked your project and I’m about to make the change that gets the DDP collection data straight into the Redux store, which worked quite nicely!

I noticed that you kept things global. Are things currently loading according to Meteor naming rules? Do you mind if I put the modules inside an imports folder so things are loaded lazily and main.jsx becomes the entry point?

Edit: oh wait, nevermind about the file structure.