How to pass in redux toolkit more that one arguments in an action? - redux-toolkit

In the docs it is stated that you can set an action with one argument as a payload like this:
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
But what if I have more arguments?

I could not find anything but a segment (1:41') in ​Justin's video here
So this is how it's done:
authenticate: (state, { payload }: PayloadAction<{
token: string;
userId: string;
expiryTime: number;
userEmail: string
}>) => {
state.token = payload.token
state.userId = payload.userId
state.expiryTime = payload.expiryTime
state.email = payload.userEmail
state.triedAutoLogin = true
},
Note: above the action is being destructured. So we do not have to do action.payload.token
Thanks

Related

Redux toolkit query. useLazyQuery

Try to understand how to structure queries.
What I have now:
File for CRUD:
export const PromoService = apiClient.injectEndpoints({
endpoints: (build) => ({
fetchPromoById: build.query<
Promotion,
{ ppeType: PpeType; id: string }
>({
query: ({ ppeType, id }) => apiQuery(ppeType, 'fetchPromoById', id),
providesTags: (_result, _err) => [{ type: 'Promo' }],
}),
fetchPromoByCategory: build.mutation<
PromotionData,
{ ppeType: PpeType; type: string; bannerId: string }
>({
query: ({ ppeType, type, bannerId }) => ({
url: apiQuery(ppeType, 'fetchPromoByCategory'),
method: 'POST',
body: fetchPromoByCategoryBody(type, bannerId),
}),
invalidatesTags: ['Promo'],
}),
}),
});
export const { useLazyFetchPromoByIdQuery, useFetchPromoByCategoryMutation } =
PromoService;
File for slices:
const initialState: PromotionState = {
chosenPromotion: {} as Promotion,
promoList: [],
};
const promoSlice = createSlice({
name: 'promo',
initialState,
reducers: {
setChosenPromotion: (state, action: PayloadAction<Promotion>) => {
state.chosenPromotion = action.payload;
},
setPromoList: (state, action: PayloadAction<Promotion[]>) => {
state.promoList = action.payload;
},
},
});
Component:
const [fetchPromoByCategory, { isLoading, data: categoryData }] =
useFetchPromoByCategoryMutation({
fixedCacheKey: 'shared-update-promo',
});
const [trigger, result] = useLazyFetchPromoByIdQuery();
const chosenPromo = result.data;
useEffect(() => {
chosenPromo && dispatch(setChosenPromotion(chosenPromo));
}, [chosenPromo]);
There is no problem get data from useMutation in different components skipping the stage of store data via reducer.
Just use fixedCacheKey and it works fine.
Is it possible to use similar approach for getting data in different components with useLazyQuery?
I use additional dispatch to store data from useLazyQuery but I'm sure it's not appropriate approach.
It is perfectly valid to have multiple different query cache entries at once, so useLazyQuery will not initialize to one of them - it will get it's arguments once you call the trigger function.
It looks like you should use useQuery here, sometimes with the skip parameter when you don't want anything fetched from the start.

Different Read/Write types for FirestoreDataConverter

Is there a way to use different types for reading and writing data using the FirebaseDataConverter?
The typing of FirebaseDataConverter<T> suggest that there should only be a single type T, which is both what you would get back when querying and what you should provide when writing.
But in the scenario outlined below, I have two types, InsertComment which is what I should provide when creating a new comment, and Comment, which is an enriched object that has the user's current name and the firebase path of the object added to it.
But there is no way to express that I have these two types. Am I missing something?
type Comment = { userId: string, userName: string, comment: string, _firebasePath: string }
type InsertComment = { userId: string, comment: string }
function lookupName(_id: string) { return 'Steve' }
const commentConverter: FirestoreDataConverter<Comment> = {
fromFirestore(snapshot, options) {
const { userId, comment } = snapshot.data(options)
return {
userId,
comment,
name: lookupName(userId),
_firebasePath: snapshot.ref.path,
} as any as Comment
},
// Here I wish I could write the below, but it gives me a type error
// toFirestore(modelObject: InsertComment) {
toFirestore(modelObject) {
return modelObject
},
}
const commentCollection = collection(getFirestore(), 'Comments').withConverter(commentConverter)
// This works great and is typesafe
getDocs(commentCollection).then(snaps => {
snaps.docs.forEach(snap => {
const { comment, userName, _firebasePath } = snap.data()
console.info(`${userName} said "${comment}" (path: ${_firebasePath})`)
})
})
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This gives me the type-error: that fields "userName, _firebasePath" are missing
addDoc(commentCollection, { comment: 'Hello World', userId: '123' })
I found a workaround, but I don't think this ought to be the way it should be done. It feels hacky.
Basically, I make two DataConverters, one for reading and one for writing.
I make the one for reading the default one, and when I need to write, I overwrite the read-converter with the write-converter.
function createReadFirestoreConverter<T>(validator: Validator<T>): FirestoreDataConverter<T> {
return {
fromFirestore(snapshot, options) {
return validator({ ...snapshot.data(options), _id: snapshot.id, _path: snapshot.ref.path })
},
toFirestore() {
throw new Error('Firestore converter not configured for writing')
},
}
}
function createWriteFirestoreConverter<T>(validator: Validator<T>) {
return {
fromFirestore() {
throw new Error('Firestore converter not configured for reading')
},
toFirestore(modelObject: any) {
return validator(modelObject)
},
} as FirestoreDataConverter<any>
}
const installedComponentConverterRead = createReadFirestoreConverter(installedComponentValidator)
const installedComponentConverterWrite = createWriteFirestoreConverter(newInstalledComponentValidator)
const readCollection = collection(getFirestore(), `MachineCards/${machineCard._id}/Components`).withConverter(installedComponentConverterRead)
// If I need to write
const docRef = doc(readCollection, 'newDocId').withConverter(installedComponentConverterWrite)

Why mockReturnValueOnce seems not to work?

I'm trying to use Jest along Meteor, but I'm getting the following problem while trying to implement tests using Collection.find().fetch().
I'm mocking the function find and fetch, as you can see below. However, the value return by fetch is always the first one.
Shouldn't the value be different every time, since I'm using mockReturnValueOnce?
import { describe, test } from "#jest/globals";
import { Users } from "../../../collections"; // a Mongo collection
describe("#Users test suite", () => {
const mock1 = {
userId: "111aaa",
email: "user1#test.com",
};
const mock2 = {
userId: "222bbb",
email: "user1#test.com",
};
const mock3 = {
userId: "333ccc",
email: "user1#test.com",
};
test("Fetch test", () => {
Users.find.mockImplementation(() => ({
fetch: jest
.fn()
.mockReturnValueOnce(mock1)
.mockReturnValueOnce(mock2)
.mockReturnValueOnce(mock3),
}));
// getting same result in every fetch call
console.log(Users.find().fetch()); // { userId: '111aaa', email: 'user1#test.com' }
console.log(Users.find().fetch()); // { userId: '111aaa', email: 'user1#test.com' }
console.log(Users.find().fetch()); // { userId: '111aaa', email: 'user1#test.com' }
});
});
Thanks in advance!

How can i make a incomming object in typescript to a class?

i have a function that talks to a server. And one of these getting parametes is called "userstate". This userstate is an object. I get from this object this:
userstate = {
'username' = '',
'display-name' = ''
};
How can I make a class of this? I have at the moment this:
extends class Userstate {
username: string;
displayName: string;
constructior() {
this.username = this['username'];
this.displayName = this['display-name'];
}
}
And in my function I do this:
let get_user_stuff = (userstate: Userstate) => {
console.log(userstate.username);
console.log(userstate.displayName);
};
And my console says this:
darky_chan
undefined
I hope you can help me :)
Thanks in advance
Are you looking for something like this:
class Userstate {
username: string;
displayName: string;
constructor(jsonObject: any) { // Notice the json object passed to constructor
this.username = jsonObject.username;
this.displayName = jsonObject['display-name'];
}
}
let convertToInstance = (jsonObj: any) => (new Userstate(jsonObj));
let instance = convertToInstance({
'username': 'name',
'display-name': 'd-name'
});
console.log(instance);
console.log(instance.displayName);
According to typescript specs you can have a property name containing invalid identifiers:
class Userstate
{
username: string;
"display-name": string;
}
The only problem is that you will have to access them using indexer:
const u = new Userstate();
u["display-name"] = 23;

How to use URLs like '/update/:id' as KendoUI datasource?

I read the documentation but found nothing related to setting parameters in dataSource urls. Is it possible to achieve that?
Thx in advance.
Yes, it is possible. The urls defined in the DataSource.transport might be a function. This function receives (for update) as first argument the data being updated (the model) and returns the string that should be used as URL.
Composing the URL for what you want to do is:
var ds = new kendo.data.DataSource({
transport: {
read: {
url: function () {
return 'read';
}
},
update: {
url : function (item) {
return 'update/' + item.id;
}
}
}
});
The answer seems to be vague on 'item.'
Just note that 'item' is an object. In fact anything passed in to read has to be an object, that's what Kendo expects. If you pass anything else into read, like a string, it will convert it into an object which isn't what you want. So, the solution is as follows:
_viewModel: kendo.observable({
items: new kendo.data.DataSource({
transport: {
read: {
url: function (args) {
var urlParm = '?take=' + 1 + '&skip=0&page=1&pageSize=' + 1;
return CGI_ISD._base + 'api/executionsummary/executiondetails/' + args.msgId + urlParm;
},
dataType: "json"
},
},
schema: {
data: function (response) {
return response.AggregateData.Data;
}
}
}),
}),
_reload: function (msgId) {
this._viewModel.items.read({msgId: msgId});
}
Short answer:
Nope.
Long answer:
Parameters are passed either inline with the url parameter of the transport object...
var id = 'abc123';
var ds = new kendo.data.DataSource({
transport: {
read: {
url: 'api/employees?id=' + id
}
}
});
...or they are passed in the data parameter of the transport object.
var id = 'abc123';
var ds = new kendo.data.DataSource({
transport: {
read: {
url: 'api/employees',
data: {
id: id;
}
}
}
});
or
var id = 'abc123';
var ds = new kendo.data.DataSource({
transport: {
read: {
url: 'api/employees',
data: function () {
return { id : id };
}
}
}
});