How to handle one-to-many relationships in Flux stores - reactjs

I'm just starting to use flux (with redux for now) and am wondering how relationships are supposed to be handled.
For an example we can use Trello that has boards with columns that contains cards.
One approach would be to have one store/reducer for boards and have all the data in it there but that means some very fat stores since they would have to contain all the actions for columns and cards as well.
Another approach i've seen is separating nested resources into for example BoardStore, ColumnStore and CardStore and use their ids as reference.
Here's an example of where I am a bit confused: you could have an action creator called addCard that does a request to the server to create a card with all the data. If you are doing optimistic update, you would have created a card object in one of your store before but you can't know the id it will have until you get back the request.
So in short:
Firing addCard
addCard does a request, in the meantime you return an action of type ADD_CARD_TEMP
you get the request and return an action of type ADD_CARD where the store/reducer changes the id.
Is there a recommended way to deal with this case? Nested store/reducers look a bit silly to me but otherwise you end up with very complex stores so it looks like a compromise really.

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.

Related

How fetch data in from firebase database from two differents sublevel?

I am trying to make a request to get all the events of a user, then get the detail of this events in a list. I don't find a right solution to do that.
Database
Actions index
So at the moment, I only get the user's travel, but not the detail of each event that the user have.
Thank you for your help
You'll need to do another on() or once() for each event inside your current callback, and load the additional data. This process is known as a client-side join. Then within the inner loop you can dispatch the results, either on each load or when all are loaded.
Code (untested, so there may be typos):
usersRef.child(uid).child("events").on("value", snapshot => {
var promises = []
snapshot.forEach(eventSnapshot => {
promises.push(eventsRef.child(eventSnapshot.key).once("value"));
})
Promise.all(promises).then(eventSnapshots => {
// eventSnapshots contains the details of all events
return eventSnapshot.map(eventSnapshot => eventSnapshot.val());
}).then(events => {
dispatch({ type: FETCH_EVENTS, payload: events });
});
})
Alternatively you can duplicate the minimal data for each event under /users/$uid/events/$eventid`. This duplicates data, but prevents the need for client-side joins. This type of read-vs-write trade-off is very common when using NoSQL databases. For strategies for the duplicated data up to date, see How to write denormalized data in Firebase.

My flux store gets re-instantiated on reload

Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.

Is there a convention for Flux messages sent via the Dispatcher?

I'm building my first React front end and see a number of conventions for messages sent via the Dispatcher. e.g.
{
type: ActionTypes.RECEIVE_RAW_MESSAGES,
rawMessages: rawMessages
}
https://github.com/facebook/flux/blob/master/examples/flux-chat/js/actions/ChatServerActionCreators.js#L21
and
{
source: 'VIEW_ACTION',
action: action
}
http://facebook.github.io/react/blog/2014/09/24/testing-flux-applications.html#putting-it-all-together
What is the best message format to use & why?
The short answer is, it probably doesn't really matter—as long as your stores look for the right data. I always use the following format:
{
type: 'ACTION_TYPE', // usually defined by a constant
payload: { ... } // a payload of JSON serializable types
}
If your app needs to distinguish between actions that are initiated by the user and actions that come from the server or some other source, you may considering adding a source key; I personally use separate action types or data within the payload for this purpose.
I always make payload an object (never a raw value) so that data can be added easily without changing receiving sites. For example, instead of
dispatch({type: ACTION_TYPE, payload: id})
I would recommend
dispatch({type: ACTION_TYPE, payload: {id: id}})
Of course, some of this may be dictated by which flux implementation (if any) that you use. The Facebook dispatcher is very agnostic (you can send pretty much anything you want), but some implementations require specific keys (like type, etc).
Flux Standard Action is a project to standardize Flux actions.
In short, an action must have a type, and may have a error, payload, or meta.
Examples
{
type: 'ADD_TODO',
payload: {
text: 'Do something.'
}
}
For an error:
{
type: 'ADD_TODO',
payload: new Error(),
error: true
}
The meta field is indented for "any extra information that is not part of the payload". I haven't seen this used, so I'd try to stick to payload.

Can angular resource do a bulk restful operation?

Say I have a todo application and clicking a checkbox on any individual Todo marks it as complete and does a PUT operation.
Then there is a checkbox to 'mark all complete' or 'mark all incomplete'. This should mark every todo as completed/incomplete, regardless of what its individual status is.
When using angular-resource, what is the best practice way to update all the items. Is it possible to do it in a single bulk request and have all the items updated? Or would I be better off just updating each one individually?
You could extend your Angular resource by providing a custom action, for example:
var Todo = $resource('api/todo/:todo_id', {todo_id: '#id'}, {
markAllComplete: { method: 'POST', params: { complete: true }, isArray: true }
}
and then in your controller doing:
// Assuming your todos have been fetched and are stored
// in the $scope.todos variable...
Todo.markAllComplete($scope.todos);
The only thing (and arguably the hardest thing) left to do would be to code your backend to accept a POST to 'api/todo' and mark all the referenced todos as completed.

Persisting data between routes without going to the server?

I am working on a log in for my backbone application and came on an issue I am not sure how to solve without making a call to the server. This brought up a discussion in my team about what the way other folks are handing this kind of thing in backbone because we think we will be running into a similar thing moving forward.
It's Friday and I'm probably just brain dead, but here goes...
We have a User Model. The login method of the View creates a new user Model and call's it's login method passing in the user's credentials and a callback function which has an object that contains the users information.
Here is the login method for our View:
login: function(event){
event.preventDefault();
var user = new App.User;
user.login($('#username').val(), $('#password').val(),
(function(msg) {
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
console.log("Prepared data: " + JSON.stringify(data));
App.router.navigate('home',data);
} else {
console.log("Authenticate unsuccessful: " + JSON.stringify(msg));
}
}).bind(this),
function(msg) {
// failure callback
console.log("Authenticate communication failure: " + JSON.stringify(msg));
alert("Communication fail!");
App.router.navigate('login',{trigger:true});
});
}
What we are trying to figure out is how to best make this Model data available to another route (home) so we can use it in the View.
So I have this router:
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
home: function(data){
console.log(data);
}
Once we have logged the user in we need to update the route and have access to that users data, but don't want to have to make a trip back to the server to fetch it.
I am concerned because we are building a kind of "wizard" where the user may need to move forward and backward through some steps and I don't want to have to hit the server every time they navigate through the application, but it's seeming like we are going to either need to save stuff to a global variable (don't want to do this) or make a trip back to the server every time. I'm sure others have had to deal with similar issues. Just looking for some insight.
Thanks!
"it's seeming like we are going to either need to save stuff to a global variable (don't want to do this)"
To state the obvious: You're going to need to preserve state. Your options are either to transfer the state to the server and back, or hold state on the client. Since you've already identified that you don't want to pass the state via a server, you're left with preserving state between different pages (routes) on the client.
And that's what global variables are for. It sounds icky, I know, but it's also one of the main benefits that Single-Page Applications bring to the table. Statefulness. And that state will always be held by some global object.
There are better and worse ways of managing state. Having a global variable called data that you keep assigning and reassigning is obviously the worst way. You should figure out a pattern that makes sense for your requirement.
If I understood your code sample correctly, what you want to store is the information about the current user. It also seems that you already have a global variable App. It occurs to me that it would be a good idea to keep some kind of session info:
login: function(data){
doLogin({
success: function(userData) {
App.session.currentUser = userData;
App.router.navigate('home', { trigger:true });
}
});
},
home: function(data){
if(!App.session || !App.session.currentUser) {
App.router.navigate('login', { trigger:true });
return;
}
var user = App.session.currentUser;
//do something with user
}
State is not necessarily evil. What's evil is depending on global state throughout the application, which leads easily to untestable spaghetti code. But if you resolve the state dependency as "high up" in the chain as possible (e.g. in the Router), and pass the values down using constructors and method arguments, you can still keep the testability and side-effectlessness in the rest of the codebase.
Sorry that I don't have a silver bullet for you. There are some libraries, Backbone.StateManager among them, which can help in managing state, transitions and such, but essentially they don't do anything you can't do for yourself.
Use localStorage!
Modify your code to do the following:
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
var dataString = JSON.stringify(data);
console.log("Prepared data: " + dataString;
window.localStorage.setItem("userdata",dataString);
App.router.navigate('home',data);
Now whenever you need to check if the user is logged in, do the following:
try {
var userData = window.localStorage.getItem ("userdata");
} catch (e) {
// Do something
}
The try-catch is necessary to make sure that your code doesn't barf if the authentication has never been successful.
Its too late to reply but there is another much better way to do this depending on the router engine you are using and with no local variables.
I would try to give a general example using backbone which applies to all.
Generally your router would be in one place where things are being handled. Assuming its backbone lets have the routes defined as follows.
var router = Backbone.Router.extend({
routingData: {}, // this will have the routing data
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
navigate: function(url, data) {
this.routingData["data"] = data; // whenever navigation is done, this will be reset
//This is the routing code whichever the f/w may be.
Backbone.Router.prototype.navigate(route, { trigger: true });
},
home: function(data) {
var params = this.routingData["data"]; //retreiving the routing params
console.log(params);
}
})
Now if you want to pass data, you can do
router.navigate(<URL>,<SOME DATA>)

Resources