Redux docs suggest using normalizr to design the shape of the state like this:
{
entities: {
cards: {
42: {
id: 42,
text: 'Hello',
category: 2
},
43: {
id: 53,
text: 'There?',
category: 1
},
}
categories: {
1: {
id: 1,
name: 'Questions'
}
2: {
id: 2,
name: 'Greetings'
},
}
},
filter: 'SHOW_ALL',
allCardsList: {
isFetching: false,
items: [ 42, 43 ]
},
}
Naturally, this would split into three composable reducers (filter, allThingsList and entities), but it seems to me that I'd want to write separate reducers for entities.cards and entities.categories.
Is there a way to split management of entities into subreducers that would allow composition like this:
let rootReducer = combineReducers({
entities: {
things,
categories
},
filter,
allCardsList
});
Are there any advantages to keeping the cards and categories in entities, instead of keeping on the root level (which will allow composition using combineReducers)?
{
cards: { ... },
categories: { ... },
filter: 'SHOW_ALL',
allCardsList: { ... }
}
Is there a way to split management of entities into subreducers that would allow composition like this?
Sure! combineReducers() just gives you a reducer so you can use it several times:
let rootReducer = combineReducers({
entities: combineReducers({
things,
categories
}),
filter,
allCardsList
});
The shopping-cart example in Redux repo demonstrates this approach.
Are there any advantages to keeping the cards and categories in entities, instead of keeping on the root level?
It’s up to you but I find the structure you suggest easier to work with. It’s indeed easier for understanding to group all entities under a single key, and it is possible with combineReducers() as I show above.
Related
I just want to Publish the relational Data for a Publication to client, but the issue is my Relational Data field is array of ID's of a Different Collection, I tried Different Packages but all works with single Relational ID but not working with Array of relational ID's, let assume I have two Collection Companies and Meteor.users below is my Company Document Looks like
{
_id : "dYo4tqpZms9j8aG4C"
owner : "yjzakAgYWejmJcuHz"
name : "Labbaik Waters"
peoples : ["yjzakAgYWejmJcuHz", "yjzakAgYWejmJcuHz"],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
here you can see peoples field contains the user ID's as Array, so How I publish this userId's as user Documents, as for example I tried the most popular meteor package named publishComposit, when I tried Loop in Children's find, I got undefined in children i.e below
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
find(company) {
let cursors = company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
and if I implement async loop in children's find I got error like below code
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
async find(company) {
let cursors = await company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
the error occured in above code is Exception in callback of async function: TypeError: this.cursor._getCollectionName is not a function
I don't know what I am exactly doing wrong here, or implementing package function not as intended any help will be greatly appropriated
EDIT: my desired result should be full user documents instead of ID no matter it mapped in same peoples array or as another fields I just want as below
{
_id: "dYo4tqpZms9j8aG4C",
owner: "yjzakAgYWejmJcuHz",
name: "Labbaik Waters",
peoples: [
{
profile: {firstName: "Abdul", lastName: "Hameed"},
_id: "yjzakAgYWejmJcuHz"
}
],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
I ran into a similar problem couple of days ago. There are two problems with the provided code. First, using async; it's not needed and rather complicates things. Second, publishComposite relies on receiving one cursor not multiple within its children to work properly.
Below is a snippet of the code used to solve the problem I had, hopefully you can replicate it.
Meteor.publishComposite("table.conversations", function(table, ids, fields) {
if (!this.userId) {
return this.ready();
}
check(table, String);
check(ids, Array);
check(fields, Match.Optional(Object));
return {
find() {
return Conversation.find(
{
_id: {
$in: ids
}
},
{ fields }
);
},
children: [
{
find(conversation) {
// constructing one big cursor that entails all of the documents in one single go
// as publish composite cannot work with multiple cursors at once
return User.find(
{ _id: { $in: conversation.participants } },
{ fields: { profile: 1, roles: 1, emails: 1 } }
);
}
}
]
};
});
I have a key in dynamo that has two Global Secondary Indexes with different range keys. Like this:
const productSchema = new Schema(
{
productCategory: {
type: String,
index: [{
global: true,
rangeKey: 'serialNumberEnd',
name: 'productCategory',
throughput: { read: 1, write: 1 },
project: ['quantity'],
},
{
global: true,
rangeKey: 'orderType',
name: 'openOrders',
throughput: { read: 1, write: 1 },
project: true,
}],
},
{
throughput: { read: 1, write: 1 },
useNativeBooleans: true,
saveUnknown: true,
},
);`
Trying to use the 'name' does not seem to be the answer.
Resource.query('openOrder').eq(id)
How am I supposed to distinguish between the two GSI's on the same Key in a resource when constructing a query?
EDIT - Added additional context to the schema, moved answer to the answer section
In this case you don't want to be using the name property of the index. You want to be using the actual property to do this.
For example:
Resource.query('openOrder').eq(id)
.where('serialNumberEnd').lt(5)
I don't know your entire schema so it's not an exact match of what you want to do. But something like that should work.
You can also look at this test in the source code for an example of using multiple indexes on one property and querying.
const result = await Product.query(
{
hash: { productCategory: { eq: 'tags' } },
range: { orderType: { eq: 'sale' } },
},
{ indexName: 'openOrders' },).exec();
I was a bit slow at getting to this syntax. Hope this helps someone else.
EDIT: A little cleaner syntax/more context to the schema
const result = await Product.query('productCategory', { indexName: 'openOrders' }).eq('tags')
.where('orderType').eq('purchase')
.exec();
I had no luck getting Dynamoose to recognize the correct index based on the Range found in a 'where' statement. The { indexName: 'value' } is needed.
With a structure of
/archive: {
$userId: {
$archiveKey: {
foo: 1
},
...
},
...
}
Where $userId references a user id and $archiveKey is dynamic, created by .push().
Is it possible to query the archive ref and get all archiveObjects where foo = 1 ? Or do I need to fetch down the whole table, manually dig into $userId and extract the archiveObjects I'm looking for?
Firebase queries can nowadays query nested paths, but the paths cannot be dynamic.
So if you know the uid, you can query that user's archives with:
ref.child(authData.uid).orderByChild('foo').equalTo(1).on(...
If you don't know the uid, then you'll have to create a data structure that allows you to do the lookup:
archive_category_to_uids: {
foo: {
1: {
uid1: true,
uid2: true
}
}
}
A more common way is to separate the archives into their own top-level list and have both users and categories refer to that:
users: {
userId1: {
archiveKey1: true,
...
},
...
},
archives: {
archiveKey1: {
foo: 1,
uid: uid1
},
...
},
archiveCategories: {
foo: {
1: {
archiveKey1: true,
archiveKey2: true
}
}
}
Now you can get find the archives with:
ref.child('archiveCategories/foo/1').once('value', function(keys) {
keys.forEach(function(key) {
ref.child('archives').child(key.key()).once('value', function(snapshot) {
console.log(snapshot.val());
});
};
});
This process is called denormalization and is quite common in NoSQL databases. You're modeling the data for how your application needs to consume it. For more on this and other common patterns, I recommend reading this article on NoSQL data modeling.
I'm using Redux and Normalizr.
I have a state which looks like:
{
entities: {
users: {
1: {...},
2: {...}
}
},
data: {
users: [1,2]
}
}
One of my API endpoints returns an object that includes a single user and some other data:
data = {
user: {id: 3, ...}
otherstuff: {...}
}
const user = new Schema('users');
normalize(data, {user: user})
This returns
{
entities: {
users: {
3: {id: 3, ...}
}
},
result: {
user: 3
}
}
However my data reducer expects to merge in a users array (of ids):
function data(state = {}, action) {
if (action.response && action.response.result) {
return deepmerge(state, action.response.result)
}
return state
}
Ideally I'd address this issue in the normalisation process rather than changing the reducer. Is there an easy way to either get normalizr to parse {user: {id: 3}} into {result: {users: [3]}}? It already changes the entities key to users.
If not, is there a clean and generic reducer-level solution to having this problem across a variety of entity type names?
Imagine you have 2 components: TasksList and OverdueTasksList. The number of tasks is big, so you cannot load all tasks with 1 network request. This means that they both need to fetch data individually.
What's the best way to organize data with Redux in this case?
Here's my idea:
{
tasks: {
t1: {
id: "t1",
name: "Task 1",
},
// ...
},
tasksList: {
isFetching: false,
error: null,
tasks: ["t1", ...]
},
overdueTasksList: {
isFetching: false,
error: null,
tasks: ["t5", ...]
}
}
This way, if you edit a task object from one component, it will be reflected in the other component as well.
What I don't like with this pattern is that you have to create a new reducer for every component that fetches data.
Could you have 'overdue' as a property of task instead? therefore you could merge the 2 reducers into one:
const initialState = [];
tasks: function(state = initialState, action){
switch(action.type){
case receiveOverduetask:
return [...state, action.overdueTask];
case receiveTask:
return [...state, action.task];
default:
return state;
}
}
You can still have the 2 separate requests, just make sure that they set the overdue property accordingly.