React/Flux app data structure - reactjs

i'm building an Gmail-like email browser client app prototype and i need a little help/advice structuring my React/Flux app. I decided to use pure Flux to get a better idea of how it works.
It's a simple email client with a list of letters, grouped by folders and tags and an ability to add letters to favorites.
So, i have a LettersStore containing an array of letters. The single letter data object looks something like this
{
id: 0,
new: true, //unread
checked: false,
starred: false,
folder: "inbox", //could be 'sent', 'spam', 'drafts'
sender: "Sender Name",
subject: "Re:",
snippet: "Hello there, how are you...",
body: "Hello there, how are you doing, Mike?",
date: "02.19.2016 16:30",
tags:["personal", "urgent"]
}
So what i'm trying to achieve is to let users navigate through folders (inbox, sent, drafts, spam) and filters (starred, tag, etc.)
In both folders and filters there has to be a way to select (check) some/all letters. The view state depends on how many letters are selected (the Select-all checkbox update, just like on Gmail). When the user selects a letter, the Flux action is being triggered and the state of the app updates.
The controller-view on top of the app does all the calls to the LettersStore public methods and passes the data down as props, but i'm not sure, what public methods the LettersStore should have. Currently it has:
emitChange()
addChangeListener()
removeChangeListener()
getAll() //returns array
areSomeLettersInFolderChecked(folderName) //returns bool
areAllLettersInFolderChecked(folderName) //returns bool
countNewLettersInAllFolders() //returns object
This works ok with folders, but when it comes to filters, it doesn't make sense anymore, since a starred letter is in some folder, and i feel like it's not the right thing to add specific methods like areSomeLettersInFilterChecked(filterType) etc.
Also, just like in Gmail, there has to be a way to select letter in the "Starred" filter, which belongs to the "Inbox" folder, then navigate to "Inbox" folder and keep that letter selected.
Maybe i should move the areSomeLettersInFolderChecked-like stuff to the component level?
I'm sure here has to be a proper way of doing it. Thanks in advance!

Rather than trying to encapsulate all the possible states and filters into your letter objects, keep it dumb. Normalize it and use supporting data structures to represent the other characteristics.
I'd strip it down to just the following properties:
{
id: 0,
sender: "Sender Name",
subject: "Re:",
snippet: "Hello there, how are you...",
body: "Hello there, how are you doing, Mike?",
date: "02.19.2016 16:30",
tags:["personal", "urgent"]
}
Your LetterStore can stay the same, or alternatively you could use an object or map to store letters against their id's for quick lookups later.
Now we need to represent the properties we removed from the message.
We can use individual sets to determine whether a message belongs to the new, checked and starred categories.
For instance, to star a message, just add it's id to the starred set.
var starred = new Set();
starred.add(message.id);
You can easily check whether a message is starred later on.
function isStarred(message) {
return starred.has(message.id);
}
The pattern would be the same for checked and unread.
To represent folders you probably want to use a combination of objects and sets.
var folders = {
inbox: new Set(),
sent: new Set(),
spam: new Set(),
drafts: new Set()
}
Simplifying your structures into these sets makes designing queries quite easy. Here are some examples of the methods you talked about implemented with sets.
function checkAll() {
messages.forEach(function(message) {
checked.add(message.id);
});
return checked;
}
function isChecked(message) {
return checked.has(message.id);
}
function inFolder(name, message) {
return folders[name].has(message.id);
}
// is message checked and in inbox
if(isChecked(message) && inFolder('inbox', message)) {
// do something
}
It becomes easy to construct complex queries, simply by checking whether messages belong to multiple sets.

Related

Can I check if a value is only pushed if a certain field value is not filled already?

I am trying to make a Meteor app to let users push a value to the database. It works ok, but there a small issue. As soon a certain user has pushed his information, i don't want to let the same user create another entry. Or this must be blocked, or the value the user is pushing must be overwritten for the value he is posting the second time. Now I get multiple entry's of the same user.
Here is my code. Hope you can help me here. Thanks in advance.
Estimations.update(userstory._id, {
$addToSet: {
estimations: [
{name: Meteor.user().username, estimation: this.value}
]
}
});
From the mongo docs
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
Since your array elements are objects the value is the entire object, not just the username key. This means a single user can create multiple name, estimation pairs as long as the estimation value is different.
What you can do is remove any value for the user first, then reinsert:
var username = Meteor.user().username;
Estimations.update({ userstory._id },
{ $pull: { estimations: { name: username }}}); // if it doesn't exist this will no-op
Estimations.update({userstory._id },
{ $push: { estimations: { name: username, estimation: this.value }}});
By way of commentary, you've got a collection called Estimations that contains an array called estimations that contains objects with keys estimation. This might confuse future developers on the project ;) Also if your Estimations collection is 1:1 with UserStorys then perhaps the array could just be a key inside the UserStory document?

How to handle save states in React/Flux?

I've been working with react/flux for a few weeks now and while I feel like I've got a pretty good handle on everything from async loading to updating props/states/etc, one thing that is still bothering me is how to handle save states.
For example, when loading data, I just have an isLoading boolean parameter in my store that gets passed to my components. But when I try and post an updated object to the server, it's trivial to:
fire the update action
display a "save in progress" state
but figuring out the result of the update action seems to be way more difficult.
Probably the most applicable post I've seen on this is in Fluxxor's async data guide, but their solution (adding/modifying a status property on the object) feels error-prone to me.
onAddBuzz: function(payload) {
var word = {id: payload.id, word: payload.word, status: "ADDING"};
this.words[payload.id] = word;
this.emit("change");
},
onAddBuzzSuccess: function(payload) {
this.words[payload.id].status = "OK";
this.emit("change");
},
onAddBuzzFail: function(payload) {
this.words[payload.id].status = "ERROR";
this.words[payload.id].error = payload.error;
this.emit("change");
}
Is there a better way to manage save states or is adding a status property to the object the best way?
I recommend keeping your "model stores" and "ui stores" separate, or at least accessed via different cursor positions in the same store. So, in your case you'd have one store or branch for your "word model" and then another store or branch for "word status."
While this adds some complexity in the form of breaking up logic across stores and reacting twice to the AddBuzz action, it ends up reducing (more) complexity by confining model store updates to true changes in model data and managing ui states separately.
EDIT
This is what Relay will more-or-less be doing, keeping persisted data in a separate self-managed store, leaving custom stores for nothing but ui state. Some other libraries like https://github.com/Yomguithereal/baobab also recommend this approach. The idea is that these are fundamentally different kinds of state. One is domain-specific persisted data and the other is ui-specific ephemeral application state.
It might look something like this:
model_store.js:
onAddBuzz: function(payload) {
var word = {id: payload.id, word: payload.word};
this.words[payload.id] = word;
this.emit("change");
}
ui_store.js:
onAddBuzz: function(payload) {
this.wordStates[payload.id] = 'ADDING';
this.emit("change");
}
my_view_controller.js:
// listen to both stores and pass down both words and wordStates to your views
my_word_view.js:
...
render: function() {
var word = this.props.word,
wordState = this.props.wordState;
...
}
If you don't want to emit two change events, have one waitFor the other and emit the change only from the second one.

Extracting Arrays as values in a JSON file using AngularJS

This is sort of a three part question. I have a JSON file. A few of the values in the JSON file are arrays. Keeping that in mind:
1) On any given page, I'd only want one set of values coming out of the JSON file. For example (as you'll see in code below) my JSON file is a list of attorneys. On any given bio page, I'd obviously only want one attorney's information. I'm currently, successfully, doing this by pulling back the entire JSON and then using ng-show. But this is causing some other issues that I'll explain in later points, so I'm wondering if there's something to put in the app.factory itself to only bring back the one set in the first place.
2) As mentioned, some of the values are arrays. This comes into play two ways in this situation. One of the ways is that there is an array of quotes about the attorney that I'll need to drop into a JS array so that my JS function can loop through them. Currently, I'm hardcoding the quotes for the one test attorney but I'm really trying to figure out how to make this dynamic. This is one reason I'm trying to figure out how to bring back only one attorney's information so I can then, somehow, say his quotes go into this array.
3) Another array value is a list of his specialty areas. I have another, hardcoded, JS object, associating the short terms with the display names. I realized though, that this has two issues.
a) The JS renders after the Angular, so I can't reference that JS in the Angular code
b) I have no way , anyway, to display the JS dynamically inside the Angular code.
My solution to that aspect was to create a second JSON file holding the area hash but besides being a little cumbersome, I'm also not sure how to dynamically display just the ones I want. e.g: If my attorney only specializes in securities and litigation, how would I tell the code to only display {{areas.securities}} and {{areas.litigation}}? So,I'm open to thoughts there as well.
Here is the current, relevant code. If you need more, just ask.
Thanks.
attorneys.json (irrelevant lines removed)
{"attorneys":
[
{
"id":1,
"name":"Bob Smith",
"quotes":
[
{
"id": 1,
"quote": "Wonderful guy!",
"person": "Dovie"
},
{
"id": 2,
"quote": "If ye be wanting a haggis like no other, Bob be yer man!",
"person": "Angus McLoed"
},
{
"id": 3,
"quote": "Wotta Hottie!",
"person": "Bob's wife"
}
],
"areas": ["altdispute", "litigation", "securities"],
}
]
}
...and the relevant current JS object that I'm not sure what to do with:
var practiceareas = {
altdispute: "Alternative Dispute Resolution",
businesscorp: "Businesses & Corporations",
estateplanning: "Estate Planning",
futures: "Futures & Derivatives",
litigation: "Litigation",
productliability: "Product Liability",
realestate: "Real Estate",
securities: "Securities"
}
script.js (relevant function)
var idno = 0;
/* This is what I want replaced by the Angular pull */
var quotelist = ["\"Wonderful guy!\"<br/>-Dovie", "\"If ye be wanting a haggis like no other, Bob be yer man!\"<br/>-Angus McLoed", "\"Hubba, Hubba! What a hottie!\"<br/>-Bob's wife"];
$("#bio_quotes").html(quotelist[0]);
function quoteflip(id, total){
var src1 = quotelist[idno];
$("#bio_quotes").fadeOut(500, function(){
$("#bio_quotes").html(src1).fadeIn(500);
});
idno = (id + 1) % total;
window.setTimeout(function(){quoteflip(idno, quotelist.length);}, 5000);
}
window.setTimeout(function(){quoteflip(idno, quotelist.length);}, 500);
By the way, as far as the quotes, I'm even happy to turn the JSON into a more condensed version by removing the id and consolidating the quote and author - making it an array of strings instead of mini-objects - if that makes it easier. In fact, it might be easier as far as the function anyway.
Can definitely filter things out at the service / factory using Array.filter. If you want to filter it server side, you have to have the code at server side that will do that.
Not sure what your backend store is but definitely doable.
Again, you can do this pretty easily with Array.map which let you pull specific values into a new Array. If you just want the name and quotes' quote and person name, you can definitely do this using Array .filter and .map and bind the new array to your viewmodel / scope.
Hmm.. again, I'd disagree, this look like the same issue with JavaScript array manipulation. You can definitely as part of the transformation in point 1 and 2, include this so it will transfer area to the long practice area names. The easiest way to show the relevant practice area is to map it to the long name during the transformation in the service layer.
//get matching attorney from the store by id
var matches = data.attorneys.filter(function(a) {
return a.id === id;
});
//If match found,
if (matches.length === 1) {
var result = matches[0];
//map the long name for practicing area
//use the matching attorney's area name as key
//and overwrite the result areas with the map
result.areas = result.areas.map(function(a) {
return practiceareas[a];
});
return result;
}
See this solution: http://embed.plnkr.co/xBPju7/preview
As for the fade in and fade out, I'll let you figure it out...

AngularFire - How do I query denormalised data?

Ok Im starting out fresh with Firebase. I've read this: https://www.firebase.com/docs/data-structure.html and I've read this: https://www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html
So I'm suitably confused as one seems to contradict the other. You can structure your data hierarchically, but if you want it to be scalable then don't. However that's not the actual problem.
I have the following structure (please correct me if this is wrong) for a blog engine:
"authors" : {
"-JHvwkE8jHuhevZYrj3O" : {
"userUid" : "simplelogin:7",
"email" : "myemail#domain.com"
}
},
"posts" : {
"-JHvwkJ3ZOZAnTenIQFy" : {
"state" : "draft",
"body" : "This is my first post",
"title" : "My first blog",
"authorId" : "-JHvwkE8jHuhevZYrj3O"
}
}
A list of authors and a list of posts. First of all I want to get the Author where the userUid equals my current user's uid. Then I want to get the posts where the authorId is the one provided to the query.
But I have no idea how to do this. Any help would be appreciated! I'm using AngularFire if that makes a difference.
Firebase is a NoSQL data store. It's a JSON hierarchy and does not have SQL queries in the traditional sense (these aren't really compatible with lightning-fast real-time ops; they tend to be slow and expensive). There are plans for some map reduce style functionality (merged views and tools to assist with this) but your primary weapon at present is proper data structure.
First of all, let's tackle the tree hierarchy vs denormalized data. Here's a few things you should denormalize:
lists you want to be able to iterate quickly (a list of user names without having to download every message that user ever wrote or all the other meta info about a user)
large data sets that you view portions of, such as a list of rooms/groups a user belongs to (you should be able to fetch the list of rooms for a given user without downloading all groups/rooms in the system, so put the index one place, the master room data somewhere else)
anything with more than 1,000 records (keep it lean for speed)
children under a path that contain 1..n (i.e. possibly infinite) records (example chat messages from the chat room meta data, that way you can fetch info about the chat room without grabbing all messages)
Here's a few things it may not make sense to denormalize:
data you always fetch en toto and never iterate (if you always use .child(...).on('value', ...) to fetch some record and you display everything in that record, never referring to the parent list, there's no reason to optimize for iterability)
lists shorter than a hundred or so records that you always as a whole (e.g. the list of groups a user belongs to might always be fetched with that user and would average 5-10 items; probably no reason to keep it split apart)
Fetching the author is as simple as just adding the id to the URL:
var userId = 123;
new Firebase('https://INSTANCE.firebaseio.com/users/'+userId);
To fetch a list of posts belonging to a certain user, either maintain an index of that users' posts:
/posts/$post_id/...
/my_posts/$user_id/$post_id/true
var fb = new Firebase('https://INSTANCE.firebaseio.com');
fb.child('/my_posts/'+userId).on('child_added', function(indexSnap) {
fb.child('posts/'+indexSnap.name()).once('value', function(dataSnap) {
console.log('fetched post', indexSnap.name(), dataSnap.val());
});
});
A tool like Firebase.util can assist with normalizing data that has been split for storage until Firebase's views and advanced querying utils are released:
/posts/$post_id/...
/my_posts/$user_id/$post_id/true
var fb = new Firebase('https://INSTANCE.firebaseio.com');
var ref = Firebase.util.intersection( fb.child('my_posts/'+userId), fb.child('posts') );
ref.on('child_added', function(snap) {
console.log('fetched post', snap.name(), snap.val();
});
Or simply store the posts by user id (depending on your use case for how that data is fetched later):
/posts/$user_id/$post_id/...
new Firebase('https://INSTANCE.firebaseio.com/posts/'+userId).on('child_added', function(snap) {
console.log('fetched post', snap.name(), snap.val());
});

How to handle empty arrays in Firebase?

Say person can have multiple cars, and car can have multiple accidents. So we could have:
# Person with no cars
person:
name: "Misha"
cars: []
# Person with free-accident car
person:
name "Arlen"
cars:
0:
name: "Toyota"
accidents: []
Firebase stores these people as:
person:
name: "Misha"
and
person:
name "Arlen"
cars:
0:
name: "Toyota"
So in JavaScript I have to do following to restore the empty arrays: (CoffeeScript)
if person.cars?
for car in person.cars
car.accidents = [] unless car.accidents?
else
person.cars = []
Is there a better way to handle empty arrays in Firebase without writing this needless JavaScript code?
I think that, if I understand the core question, the short answer is that there is no way to force an empty array into Firebase. However, there are some paradigms that might work better than what you have above.
Keep in mind that Firebase is a real-time environment. The number of cars and accidents can (and will) change at any time. It's best to treat everything as new data arriving in real time and avoid even thinking about exists or doesn't exist.
// fetch all the people in real-time
rootRef.child('people').on('child_added', function(personSnapshot) {
// monitor their cars
personSnapshot.ref().child('cars', 'child_added', function(carSnapshot) {
// monitor accidents
carSnapshot.ref().child('accidents', 'child_added', function(accidentSnapshot) {
// here is where you invoke your code related to accidents
});
});
});
Note how there is no need for if exists/unless type logic. Note that you would probably also want to monitor child_removed on cars and people and call ref.off() to stop listening to specific children.
If for some reason you want to stick with the static model, then forEach will become your friend:
// fetch all the people as one object, asynchronously
// this won't work well with many thousands of records
rootRef.child('people').once('value', function(everyoneSnap) {
// get each user (this is synchronous!)
everyoneSnap.forEach(function(personSnap) {
// get all cars (this is asynchronous)
personSnap.ref().child('cars').once('value', function(allCars) {
// iterate cars (this is synchronous)
allCars.forEach(function(carSnap) { /* and so on */ });
});
});
});
Note how, even with forEach, there is no need for "exists or unless" sort of logic.
I usually use the DataSnapshot function numChildren() to see if it's empty of not, like this
var fire = new Firebase("https://example.firebaseio.com/");
fire.once('value', function(data){if (data.numChildren() > 0){ /*Do something*/ });

Resources