Add item to nested array in redux-toolkit - redux-toolkit

Redux Toolkit is giving me mutation errors when trying to update state on a nested array, which I thought it was using immer to get around this and simplify the reducers.
My store looks like :
state -> forms -> sections
I want to add a section to an existing form.
My action takes a form and a section
the reducer looks like
let intialState={
forms:[]
}
const FormsReducer = createReducer(intialState, {
ADD_SECTION: (state, action) => {
const index = state.forms.findIndex(f => f.id === action.form.id);
state.forms[index].__formSections.push(action.payload);
},
A state mutation was detected inside a dispatch, in the path: FormsReducer.forms.0.__formSections.0
Yet according to the redux-toolkit documentation is should be possible to "write "mutative" immutable update logic"...
What am I doing wrong and how can I fix it?

If you return it without mutating from reducer the error will not occur
const FormsReducer = createReducer(intialState, {
ADD_SECTION: (state, action) => {
const newstate = {...state}
const index = newstate.forms.findIndex(f => f.id === action.form.id);
newstate.forms[index].__formSections.push(action.payload);
return newstate
},

Related

Mongoose Custom update on many documents of a collection

I have a collection with the following schema:
const CategorySchema = Schema({
name: String,
order: Number,
});
I'm trying to update the order field of the categories. The why I'm planning to do it is to have a local array with the ids of the categories in the order I want. Then, I'd fetch all categories (they are not many), and I'd start looping over the local array of ids. For each id, I'll locate it in the fetched array, and update the order according to the index of that id in the local array. The issue now is how to save it. Below is what I'm trying to do:
// Get all categories.
const categories = await Category.find({}, 'order');
console.log(categories);
// Get the order from the request.
const orderedItemIds = req.body.itemIds || [];
orderedItemIds.forEach((id, idx) => {
categories.find(x => x._id === id).order = idx;
});
// Save.
try {
await categories.save();
res.sendStatus(200);
} catch (e) {
console.log(e);
res.sentStatus(423);
}
When you query your categories, mongoose by default returns an array of instances of the Mongoose Document class. That means you can call their save() method whenever you mutate them.
So you can save your docs immediately after you assign the idx variable:
const orderedItemIds = req.body.itemIds || [];
orderedItemIds.forEach((id, idx) => {
const cat = categories.find(x => x._id.toString() === id);
cat.order = idx;
cat.save();
});
Note a few things about this code.
I assume that req.body.itemIds is a array of strings representing ObjectIds (e.g. '602454847756575710020545'). So In order to find a category in categories, you will need to use the .toString() method of the x._id object, because otherwise you will be trying to compare an Object and a string, which will never be true.
You can save the category right after assigning idx to cat.order without having to await it, because the next update is not depending on the save status of the previous.

Concat two observables for firestore query on multiple fields

I am trying to get a user search functionality working in my AngularFire app.
As firestore doesn't support these queries I thought it would be enough to query the fields separately
getUsersByName(searchValue: string) {
const firstNames = this.afs.collection<IUser>('user', ref => ref.orderBy('firstname').startAt(searchValue).endAt(searchValue+'\uf8ff')).valueChanges({ idField: 'id' });
const lastNames = this.afs.collection<IUser>('user', ref => ref.orderBy('lastname').startAt(searchValue).endAt(searchValue+'\uf8ff')).valueChanges({ idField: 'id' });
return concat(firstNames, lastNames);
}
This only works for the firstNames though. Only the first Observable is being used. I think I don't understand the concat operator but it's not clear to me according the docs what the current best solution would be for this problem.
you could use zip operator
const firstNames: Observable<string>
const lastNames: Observable<string>
zip(firstNames,lastNames).subscribe(
([firstName,lastName]) => { console.log(firstName,lastName);}
)
if firstNames and lastNames emit only one item, combineLatest([firstNames,lastNames]) will be more readable
great link to learn how to use these operators https://indepth.dev/posts/1114/learn-to-combine-rxjs-sequences-with-super-intuitive-interactive-diagrams
The reason this only works for first name is because of how concat works; it will only use one observable at a time until it completes, but the firestore observables are long lived and will not complete.
You should use merge instead of concat.
import { merge } from 'rxjs';
getUsersByName(searchValue: string) {
const firstNames = this.afs.collection<IUser>('user', ref => ref.orderBy('firstname').startAt(searchValue).endAt(searchValue+'\uf8ff')).valueChanges({ idField: 'id' });
const lastNames = this.afs.collection<IUser>('user', ref => ref.orderBy('lastname').startAt(searchValue).endAt(searchValue+'\uf8ff')).valueChanges({ idField: 'id' });
return merge(firstNames, lastNames);
}

Feather.js + Sequelize + postgres 11 : How to patch a jsonb column?

I would like to update several row of my db with the same object.
let say I have a column customText type jsonb which contains an array of object
here my sequelize model :
customText: {
type: DataTypes.JSONB,
allowNull: true,
field: "custom_text"
}
Now from client I send an object:
const obj = {}
const data = {
textid: "d9fec1d4-0f7a-2c00-9d36-0c5055d64d04",
textLabel: null,
textValue: null
};
obj.customText = data
api.service("activity").patch(null, obj).catch(err => console.log(err));
Like the documentation from feathers.js said if I want to replace multiple record, I send an id equal to null.
So now here come the problem, if I do that my column customText will contain the new object only but I want an array of object, so I want to push the new data in the array. How can I patch the data?
My guess is to use a hook in feathers.js and a raw query with sequelize. But I'm not sure how to do that.
I'm not really sure of my answer but this hook work :
module.exports = function() {
return async context => {
debugger;
const sequelize = context.app.get("sequelizeClient");
const customText = JSON.stringify(context.data.customText[0]);
console.log(customField);
let query =
"UPDATE activity SET custom_text = custom_text || '" +
customText +
"' ::jsonb";
console.log(query);
await sequelize
.query(query)
.then(results => {
console.log(results);
context.results = results;
})
.catch(err => console.log(err));
return context;
I still have a problem because after this hook in feathers, the patch continue so it will update my db again.. so i put a disallow() hook.
Also, with this hook i lost the abilities to listening to event
Also i have a concern with the query, i'm not sure if it's better to use :jsonb_insert over ||

Elegant way to dynamically query firestore [duplicate]

I have fetch some data from firestore but in my query I want to add a conditional where clause. I am using async-await for api and not sure how to add a consitional where clause.
Here is my function
export async function getMyPosts (type) {
await api
var myPosts = []
const posts = await api.firestore().collection('posts').where('status', '==', 'published')
.get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.data())
})
})
.catch(catchError)
}
In my main function I am getting a param called 'type'. Based on the value of that param I want to add another qhere clause to the above query. For example, if type = 'nocomments', then I want to add a where clause .where('commentCount', '==', 0), otherwise if type = 'nocategories', then the where clause will be querying another property like .where('tags', '==', 'none')
I am unable to understand how to add this conditional where clause.
NOTE: in firestore you add multiple conditions by just appending your where clauses like - .where("state", "==", "CA").where("population", ">", 1000000) and so on.
Add the where clause to the query only when needed:
export async function getMyPosts (type) {
await api
var myPosts = []
var query = api.firestore().collection('posts')
if (your_condition_is_true) { // you decide
query = query.where('status', '==', 'published')
}
const questions = await query.get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.data())
})
})
.catch(catchError)
}
For the frontend Web SDK:
Or you can look at this link for a different method:
Firestore conditional where clause using Modular SDK v9
let showPublishStatus: boolean = true
let conditionalConstraint: QueryConstraint = showPublishStatus
? where("status", "==", "published")
: where("status", "!=", "published")
let queryWebSDK = query(collection(db, "Collection"), conditionalConstraint)

Disable caching in Angular Firestore queries

I am running a firestore query to get data but the query is returning data from cached data queries earlier and then returns additional data (which was not queried earlier) in the second pass from server. Is there a way I can disable caching for firestore queries so that request goes to DB every time I query something.
this.parts$ = this.db.collection<OrderBom>('OrderBom', ref => {
let query : firebase.firestore.Query = ref;
query = query.where('orderPartLC', '==', this.searchValue.toLowerCase());
return query;
}).valueChanges();
Change that .valueChanges() to a .snapshotChanges() then you can apply a filter. See the example below.
I dont like changing default behavior (default configurations). I saw it's a desired behavior and the good practice is to show the data as soon as possible to the user, even if you refresh twice the screen.
I dont think is a bad practice to filter on fromCache === false when we dont have a choise. (In my case I do more requests after i receive this first one so due to promises and other async 'tasks' cache/server order is completly lost )
See this closed issue
getChats(user : User) {
return this.afs.collection<Chat>("chats",
ref => ref.where('participantsId', 'array-contains', user.id)
.snapshotChanges()
.pipe(filter(c=> c.payload.doc.metadata.fromCache === false)).
.pipe(map(//probaly want to parse your object here))
}
if using AngularFire2 you can try:
I read on the Internet that you can disable offline persistence - which caches your results -by not calling enablePersistence() on AngularFireStoreModule.
I have done the first and still had no success, but try it first. What I managed to do to get rid of caching results was to use the get() method from class DocumentReference. This method receives as parameter a GetOptions, which you can force the data to come from server. Usage example:
// fireStore is a instance of AngularFireStore injected by AngularFire2
let collection = fireStore.collection<any>("my-collection-name");
let options:GetOptions = {source:"server"}
collection.ref.get(options).then(results=>{
// results contains an array property called docs with collection's documents.
});
Persistence and caching should be disabled for angular/fire by default but it is not and there is no way to turn it off. As such, #BorisD's answer is correct but he hasn't explained it too well. Here's a full example for converting valueChanges to snapshotChanges.
constructor(private afs: AngularFirestore) {}
private getSequences(collection: string): Observable<IPaddedSequence[]> {
return this.afs.collection<IFirestoreVideo>('videos', ref => {
return ref
.where('flowPlayerProcessed', '==', true)
.orderBy('sequence', 'asc')
}).valueChanges().pipe(
map((results: IFirestoreVideo[]) => results.map((result: IFirestoreVideo) => ({ videoId: result.id, sequence: result.sequence })))
)
}
Converting the above to use snapshotChanges to filter out stuff from cache:
constructor(private afs: AngularFirestore) {}
private getSequences(collection: string): Observable<IPaddedSequence[]> {
return this.afs.collection<IFirestoreVideo>('videos', ref => {
return ref
.where('flowPlayerProcessed', '==', true)
.orderBy('sequence', 'asc')
}).snapshotChanges().pipe(
filter((actions: DocumentChangeAction<any>[], idx: number) => idx > 0 || actions.every(a => a.payload.doc.metadata.fromCache === false)),
map((actions: DocumentChangeAction<any>[]) => actions.map(a => ({ id: a.payload.doc.id, ...a.payload.doc.data() }))),
map((results: IFirestoreVideo[]) => results.map((result: IFirestoreVideo) => ({ videoId: result.id, sequence: result.sequence })))
)
}
The only differences are that valueChanges changes to snapshotChanges and then add the filter DocumentChangeAction and map DocumentChangeAction lines at the top of the snapshotChanges pipe, everything else remains unchanged.
This approach is discussed here