About normalize in redux real-world example - redux

In the example's src(UserPage.js):
const mapStateToProps = (state, ownProps) => {
// We need to lower case the login due to the way GitHub's API behaves.
// Have a look at ../middleware/api.js for more details.
const login = ownProps.params.login.toLowerCase()
const {
pagination: { starredByUser },
entities: { users, repos }
} = state
const starredPagination = starredByUser[login] || { ids: [] }
const starredRepos = starredPagination.ids.map(id => repos[id])
const starredRepoOwners = starredRepos.map(repo => users[repo.owner])
return {
login,
starredRepos,
starredRepoOwners,
starredPagination,
user: users[login]
}
}
I notice that there is many templates like xxx.ids.map(id => someEntities[id]),I am not sure why use this pattern to work.IMO,I would use something like import { map } from 'lodash'; someList && map(someList, item => {...}) in the container component and just pass the entities in the mapStateToProps.
So,could someone explains it's purpose?Thanks.

The standard suggestion for normalizing data in Redux is to store data items in an object, with IDs as the keys and the items as the values. However, an object doesn't have an inherent order to it. (Technically, the order of iteration for object keys should be consistent, but it's a bad practice to rely on that as the sole means of ordering.)
Because of that, it's also standard to store an array of just the IDs as well. A typical example might look like:
{
byId : {
qwerty : { },
abcd : { },
aj42913 : { }
},
items : ["qwerty", "aj42913", "abcd"],
sorted : ["abcd", "aj42913", "qwerty"],
selected : ["qwerty", "abcd"]
}
In this example, items contains all item IDs, probably in the order they were insert. sorted contains the IDs in some sort of sorted order, while selected contains a subset of the IDs.
This allows the items themselves to only be stored once, while multiple representations of those items can be saved using various arrays of IDs.
From there, you can pull together a list of the actual items by mapping over whatever array of IDs you care about, and retrieving the items by their IDs.
So, ultimately the answer is that relying just on the keys of the byId object doesn't give you any kind of ordering, and doesn't allow defining subsets of the data.

Related

Will using selectors to compute derived data from an API call perform better than doing within the reducer? (for this use-case)

Say I have a music store app where the user searches for guitars. On initial page load, I fetch a few varieties of guitars to display: (acoustic, electric, and bass). Pages of guitar results are returned together from a single API call but will never be displayed together. Therefore, they must be filtered at some point. To view different categories of guitars, the user will toggle the category they view from a react component.
There seems to be two major ways I can approach this problem with immutable and redux.
In Strategy 1, I filter the data on category when it arrives, and store it separately in the redux store. When I want to retrieve the data, I specify the category in the selector.
In Strategy 2, all API data that comes in is stored in an aggregate List "all". When I want to retrieve a particular category of guitars, I used a selector to filter and display from the aggregated data.
STRATEGY 1:
// REDUCER
export const GuitarReducer = (state, action) => {
const { payload, type } = state;
switch (type) {
case "acoustic": {
let existing = // GET EXISTING
return state.set("acoustic",
existing.concat(payload.filter(result => (result.category === "acoustic")))
)
}
case "electric": {
let existing = // GET EXISTING
return state.set("electric",
existing.concat(payload.filter(result => (result.category === "electric")))
)
}
case "bass": {
let existing = // GET EXISTING
return state.set("bass",
existing.concat(payload.filter(result => (result.category === "bass")))
)
}
}
}
// SELECTOR
export const selectCategory = createSelector(
[getCategory, getGuitarReducer],
(category, guitarReducer) => {
return GuitarReducer.get(category);
}
);
STRATEGY 2:
// REDUCER
export const GuitarReducer = (state, action) => {
const { payload, type } = state;
...
let existing = // GET EXISTING
...
return state.set("all",
existing.concat(payload)
)
}
// SELECTOR
export const selectCategory = createSelector(
[selectAllGuitars],
(category, guitars) => {
return guitars.filter(guitar => (guitar.category = category));
}
);
Will one pattern give better performance than another? What pattern better follows best practices for redux?
I have heard that it is best to prefer selectors for computing derived data, and that memoization will cache the results to use when another action is performed on the data such as toggling between tabs. Because of this, it is not clear to me which strategy to prefer.
I think selectors mainly focused of not re-computing derived data in your components (and the benefit of reusing it across other components).
Both in your example are good practices, so I would reframe it as follows. Do you want your datastore to look like in choice one or choice two (original API response). Do you want it to lazily load (choice two), or load categories for all guitars.
Choice 1
Pros
Stores in datastore in format more useful to your application.
Choice two recomputes on category change, choice one is computed at start and most likely more performant.
Cons
No access to original API response.
Performs filtering and categorizing on API request instead of lazily (Honestly not a big problem).
Choice 2
Pros
Stores in datastore original API response.
Lazily computes the required guitar category.
Cons
Performs computation again on category change. (Note reselect only has a cache size of 1).
Memoizing also takes additional memory.

How to Remove Arrays of Element in Firebase Cloud Firestore using Ionic 4

In My Cloud Firestore database structure looks like this. Now, I'd like to delete index positions based on Index 0, Index 1 like this.
const arrayLikedImagesRef = {imageurl: image, isliked: true};
const db = firebase.firestore();
const deleteRef = db.collection('userdata').doc(`${phno}`);
deleteRef.update({
likedimages: firebase.firestore.FieldValue.arrayRemove(arrayLikedImagesRef)
});
});
As explained here, “bad things can happen when trying to update or delete array elements at specific indexes”. This is why the Firestore official documentation indicates that the arrayRemove() function will take elements (strings) as arguments, but not indexes.
As suggested in this answer, if you prefer using indexes then you should get the entire document, get the array, modify it and add it back to the database.
You can't use FieldValue to remove array items by index. Instead, you could use a transaction to remove the array items. Using a transaction ensures you are actually writing back the exact array you expect, and can deal with other writers.
For example (the reference I use here is arbitrary, of course, you would need to provide the correct reference):
db.runTransaction(t => {
const ref = db.collection('arrayremove').doc('targetdoc');
return t.get(ref).then(doc => {
const arraydata = doc.data().likedimages;
// It is at this point that you need to decide which index
// to remove -- to ensure you get the right item.
const removeThisIndex = 2;
arraydata.splice(removeThisIndex, 1);
t.update(ref, {likedimages: arraydata});
});
});
Of course, as noted in the above code, you can only be sure you are about to delete the correct index when you are actually inside the transaction itself -- otherwise the array you fetch might not line up with the array data that you originally selected the index at. So be careful!
That said, you might be asking what to do given that FieldValue.arrayRemove doesn't support nested arrays (so you can't pass it multiple maps to remove). In that case, you just want a variant of the above that actually checks values (this example only works with a single value and a fixed object type, but you could easily modify it to be more generic):
const db = firebase.firestore();
const imageToRemove = {isliked: true, imageurl: "url1"};
db.runTransaction(t => {
const ref = db.collection('arrayremove').doc('byvaluedoc');
return t.get(ref).then(doc => {
const arraydata = doc.data().likedimages;
const outputArray = []
arraydata.forEach(item => {
if (!(item.isliked == imageToRemove.isliked &&
item.imageurl == imageToRemove.imageurl)) {
outputArray.push(item);
}
});
t.update(ref, {likedimages: outputArray});
});
});
(I do note that in your code you are using a raw boolean, but the database has the isliked items as strings. I tested the above code and it appears to work despite that, but it'd be better to be consistent in your use of types).

How to combine data model types with document ids?

I'm working with Firestore and Typescript.
For the data models I have types definitions. For example User could be this:
interface User {
name: string;
age: number;
}
The users are stored in the database in the users collection under a unique name/id.
In Firebase when you query a collection, the ids of the documents are available on the document reference, and do not come with the data. In a common use-case for front-end, you want to retrieve an array of records with their ids, because you probably want to interact with them and need to identify each.
So I made a query similar to the code below, where the id is merged into the resulting array:
async function getUsers(): Promise<any[]> {
const query = db.collection("users")
const snapshot = await query.get();
const results = snapshot.docs.map(doc => {
return { ...doc.data(), id: doc.id };
});
}
Now the problem is, that I have a User type, but it can't be used here because it does not contain an id field.
A naïve solution could be to create a new type:
interface UserWithId extends User {
id: string
}
And write the function like:
async function getUsers(): Promise<UserWithId[]> {}
But this doesn't feel right to me, because you would have to potentially do this for many types.
A better solution I think would be to create a generic type:
type DatabaseRecord<T> = {
id: string,
data: T
}
Thus keeping data and ids separate in the returning results:
const results = snapshot.docs.map(doc => {
return { data: doc.data(), id: doc.id };
});
... and use the function signature:
async function getUsers(): Promise<DatabaseRecord<User>[]> {}
I would favour the second over the first solution, because creating new types for each case feels silly. But I am still not sure if that is the best approach.
This seems like such a common scenario but I didn't manage to find any documentation on this. I have seen developers simply write the id in the model data, essentially duplicating the document name in its data, but that to me seems like a big mistake.
I can imagine that if you don't use Typescript (of Flow) that you just don't care about the resulting structure and simply merge the id with the data, but this is one of the reasons I really love using type annotation in JS. It forces you think more about your data and you end up writing cleaner code.

Redux state with paginated relational data

How would you approach the Redux state shape and/or reducers composition for paginated and relationnal data, such as paginated posts from a specific category fetched from a Wordpress API (eg.: .../posts?categories=11) ?
I'm currently dealing it this way:
const reducer = (state = {}, action) => {
switch (action.type) {
case 'RECEIVE_POSTS': {
const { data } = action.payload.result // posts ids
return { ...state, [action.meta.page || '1']: data }
}
default: return state
}
}
const list = (listName, predicate, getSublistName) => (state, action) => {
if (action.error || listName != predicate(action)) {
return state
} else if (getSublistName) {
const sublistName = getSublistName(action)
return { ...state, [sublistName]: reducer(state[sublistName], action) }
}
return reducer(state, action)
}
export default combineReducers({
categories: list(
'categories',
(action) => action.meta && action.meta.list,
(action) => action.meta && head(action.meta.categories)) // head comes from Lodash
),
search: list(
'search',
(action) => action.meta && action.meta.list
),
...
}
It works, but I feel that either the list higher order reducer could be improved by being agnostic in regard to a deeper nesting (which feels wrong...), or the resulting state shape is nested too much.
I can't wrap my head to get a totally flat state, except by using entries like a postsCategories array of posts objects with a post/category id and page number, but then data would be duplicated a lot (which feels also wrong...).
There are some libraries for pagination but I don't believe any of them handle this.
Is it about reducer composition or state shape? Both?
I post my own answer since I believe it's perfectly working for this specific case of paginated and relational state coming from a WP API.
I flattened my state shape by using query strings (used in requests to WP API) as keys for naming each posts lists in state. So it now looks like this :
entities: {
posts: {
byId: { ... },
byList: {
'page=1': [...], // post ids
'categories=1&page=1': [...], // post ids
'search=foo': [...] // post ids
},
},
}
Posts lists are easily shareable through all view components.
I can select a list by using params or search from React Router match or location, respectively, and get a URLs system working either with categories/foo/page/1 or categories=foo&page=1, like in WP core.
The standard approach for managing relational data in a Redux store is "normalization". See the Structuring Reducers - Normalizing State Shape section in the docs, and some of the articles in the Redux Techniques - Selectors and Normalization section of my React/Redux links list.
As far as pagination, the article Advanced Redux Entity Normalization has some useful examples of how to track multiple subsets of normalized data in the store.
It's also worth noting that there's already at least one library dedicated to providing a React/Redux interface to a Wordpress API, called kasia.

Meteor Framework Subscribe/Publish according to document variables

I have a game built on Meteor framework. One game document is something like this:
{
...
participants : [
{
"name":"a",
"character":"fighter",
"weapon" : "sword"
},
{
"name":"b",
"character":"wizard",
"weapon" : "book"
},
...
],
...
}
I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.
Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?
There are 2 possible solutions that come to mind:
1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.
My problem right now is the user typing
Collection.find({})
to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.
The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.
So anyway, you should have one subscription with data available to all the players, e.g.
Meteor.publish('players', function (gameId) {
return Participants.find({ gameId: gameId }, { fields: {
// exclude the "character" field from the result
character: 0
}});
});
and another subscription for private player data:
Meteor.publish('myPrivateData', function (gameId) {
// NOTE: not excluding anything, because we are only
// publishing a single document here, whose owner
// is the current user ...
return Participants.find({
userId: this.userId,
gameId: gameId,
});
});
Now, on the client side, the only thing you need to do is subscribe to both datasets, so:
Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);
Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.
EDIT
If your fields visibility is going to change dynamically I suggest the following approach:
put all the restricted properties in a separated collection that tracks exactly who can view which field
on client side use observe to integrate that collection into your local player representation for easier access to the data
Data model
For example, the collection may look like this:
PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:
userId : String
gameId : String
key : String
value : *
whoCanSee : [String]
*/
Publishing data
First you will need to expose own properties to each player
Meteor.publish('myProperties', function (gameId) {
return PlayerProperties.find({
userId: this.userId,
gameId: gameId
});
});
then the other players properties:
Meteor.publish('otherPlayersProperties', function (gameId) {
if (!this.userId) return [];
return PlayerProperties.find({
gameId: gameId,
whoCanSee: this.userId,
});
});
Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.
Improvements
In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:
var IntegratedPlayerData = new Mongo.Collection(null);
var cache = {};
PlayerProperties.find().observe({
added: function (doc) {
IntegratedPlayerData.upsert({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
changed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
removed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$unset: _.object([ doc.key ], [ true ])
});
}
});
This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.

Resources