Yes, using ids across multiple stores much like a relational database is the way to do it right.
In your example, let's say you want to optimistically put a new card in a particular column, and that a card can only be in one column (one column to many cards).
The cards in your CardStore might look like this:
_cards: {
'CARD_1': {
id: 'CARD_1',
columnID: 'COLUMN_3',
title: 'Go to sleep',
text: 'Be healthy and go to sleep on time.',
},
'CARD_2': {
id: 'CARD_2',
columnID: 'COLUMN_3',
title: 'Eat green vegetables',
text: 'They taste better with onions.',
},
}
Note that I can refer to a card by the id, and I can also retrieve the id within the object. This allows me to have methods like getCard(id)
and also be able to retrieve the id of a particular card within the view layer. Thus I can have a method deleteCard(id)
that is called in response to an action, because I know the id in the view.
Within the card store, you would have getCardsByColumn(columnID)
, which would be a simple map over the card objects, and this would produce an array of cards that you could use to render the contents of the column.
Regarding the mechanics of optimistic updates, and how the use of ids affects it:
You can use a client-side id that is established within the same closure that will handle the XHR response, and clear the client-side id when the response comes back as successful, or instead roll back on error. The closure allows you to hold on to the client-side id until the response comes back.
Many people will create a WebAPIUtils module that will contain all the methods related to the closure retaining the client-side id and the request/response. The action creator (or the store) can call this WebAPIUtils module to initiate the request.
So you have three actions:
- initiate request
- handle success
- handle response
In response to the action that initiates the request, your store receives the client-side id and creates the record.
In response to success/error, your store again receives the client-side id and either modifies the record to be a confirmed record with a real id, or instead rolls back the record. You would also want to create a good UX around that error, like letting your user try again.
Example code:
// Within MyAppActions
cardAdded: function(columnID, title, text) {
var clientID = this.createUUID();
MyDispatcher.dispatch({
type: MyAppActions.types.CARD_ADDED,
id: clientID,
columnID: columnID,
title: title,
text: text,
});
WebAPIUtils.getRequestFunction(clientID, "http://example.com", {
columnID: columnID,
title: title,
text: text,
})();
},
// Within WebAPIUtils
getRequestFunction: function(clientID, uri, data) {
var xhrOptions = {
uri: uri,
data: data,
success: function(response) {
MyAppActions.requestSucceeded(clientID, response);
},
error: function(error) {
MyAppActions.requestErrored(clientID, error);
},
};
return function() {
post(xhrOptions);
};
},
// Within CardStore
switch (action.type) {
case MyAppActions.types.CARD_ADDED:
this._cards[action.id] = {
id: action.id,
title: action.title,
text: action.text,
columnID: action.columnID,
});
this._emitChange();
break;
case MyAppActions.types.REQUEST_SUCCEEDED:
var tempCard = this._cards[action.clientID];
this._cards[action.id] = {
id: action.id,
columnID: tempCard.columnID,
title: tempCard.title,
text: tempCard.text,
});
delete this._cards[action.clientID];
break;
case MyAppActions.types.REQUEST_ERRORED:
// ...
}
Please don't get too caught up on the details of the names and the specifics of this implementation (there are probably typos or other errors). This is just example code to explain the pattern.