Redux toolkit query. useLazyQuery - redux-toolkit

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.

Related

React-Query useQueries hook to run useInfiniteQuery hooks in parallel

I am new to React-Query, but I have not been able to find an example to the following question:
Is it possible to use useInfiniteQuery within useQueries?
I can see from the parallel query documentation on GitHub, that it's fairly easy to set-up a map of normal queries.
The example provided:
function App({ users }) {
const userQueries = useQueries({
queries: users.map(user => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
})
})
}
If I have an infinite query like the following, how would I be able to provide the individual query options, specifically the page parameter?:
const ids: string[] = ['a', 'b', 'c'];
const useGetDetailsById = () => {
return useInfiniteQuery<GetDetailsByIdResponse, AxiosError>(
['getDetailsById', id],
async ({ pageParam = '' }) => {
const { data } = await getDetailsById(
id, // I want to run queries for `id` in _parallel_
pageParam
);
return data;
},
{
getNextPageParam: (lastPage: GetDetailsByIdResponse) =>
lastPage.nextPageToken,
retry: false,
}
);
};
No, I'm afraid there is currently no such thing as useInfiniteQueries.

Pulumi aws lambda unit test fails with "Could not find property info for real property on object: sdk"

I am trying to use Pulumi to create an AWS Lambda that manipulates a DynamoDB table and is triggered by an API Gateway HTTP request.
My configuration works perfectly when I run pulumi up, but when I run Vitest, my test passes but exits with non-zero and this message:
⎯⎯⎯ Unhandled Rejection ⎯⎯⎯
Error: Could not find property info for real property on object: sdk
I can see that the error comes from this code in Pulumi, but I can't figure out what causes it. Am I doing something wrong or is this a bug (in which case I can create an issue)?
Below is a summary that I think has all the relevant info, but there is a minimal repo demonstrating the problem here (GitHub actions fail with the problem I'm describing).
I have an index.ts file that creates a database, gateway, and lambda:
import * as aws from '#pulumi/aws'
import * as apigateway from '#pulumi/aws-apigateway'
import handler from './handler'
const table = new aws.dynamodb.Table('Table', {...})
const tableAccessPolicy = new aws.iam.Policy('DbAccessPolicy', {
// removed for brevity. Allows put, get, delete
})
const lambdaRole = new aws.iam.Role('lambdaRole', {...})
new aws.iam.RolePolicyAttachment('RolePolicyAttachment', {...})
const callbackFunction = new aws.lambda.CallbackFunction(
'callbackFunction',
{
role: lambdaRole,
callback: handler(table.name),
}
)
const api = new apigateway.RestAPI('api', {
routes: [{
method: 'GET',
path: '/',
eventHandler: callbackFunction,
}]
})
export const dbTable = table
export const url = api.url
The handler is imported from a separate file:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as pulumi from '#pulumi/pulumi';
import * as aws from '#pulumi/aws';
export default function (tableName: pulumi.Output<string>) {
return async function handleDocument(
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> {
try {
const client = new aws.sdk.DynamoDB.DocumentClient();
await client
.put({
TableName: tableName.get(),
Item: { PK: 'hello', roomId: '12345' },
})
.promise();
const result = await client
.get({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
await client
.delete({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
return {
statusCode: 200,
body: JSON.stringify({
item: result.Item,
}),
};
} catch (err) {
return {
statusCode: 200,
body: JSON.stringify({
error: err,
}),
};
}
};
}
Finally, I have a simple test:
import * as pulumi from '#pulumi/pulumi';
import { describe, it, expect, beforeAll } from 'vitest';
pulumi.runtime.setMocks(
{
newResource: function (args: pulumi.runtime.MockResourceArgs): {
id: string;
state: Record<string, any>;
} {
return {
id: `${args.name}_id`,
state: args.inputs,
};
},
call: function (args: pulumi.runtime.MockCallArgs) {
return args.inputs;
},
},
'project',
'stack',
false
);
describe('infrastructure', () => {
let infra: typeof import('./index');
beforeAll(async function () {
// It's important to import the program _after_ the mocks are defined.
infra = await import('./index');
});
it('Creates a DynamoDB table', async () => {
const tableId = await new Promise((resolve) => {
infra?.dbTable?.id.apply((id) => resolve(id));
});
expect(tableId).toBe('Table_id');
});
});
Your function is importing the Pulumi SDK, and you're trying to set the table name as a pulumi.Output<string>
Using the Pulumi SDK inside a lambda function isn't recommended or support.
I would recommend removing the Pulumi dependency from your function
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
export default function (tableName: string) {
return async function handleDocument(
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> {
try {
const client = new aws.sdk.DynamoDB.DocumentClient();
await client
.put({
TableName: tableName.get(),
Item: { PK: 'hello', roomId: '12345' },
})
.promise();
const result = await client
.get({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
await client
.delete({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
return {
statusCode: 200,
body: JSON.stringify({
item: result.Item,
}),
};
} catch (err) {
return {
statusCode: 200,
body: JSON.stringify({
error: err,
}),
};
}
};
}
The callback function should take take non inputty types, which should then remove the need to call the Pulumi SDK during your test suite. You can see an example here:
https://github.com/pulumi/examples/blob/258d3bad0a00020704743e37911c51be63c06bb4/aws-ts-lambda-efs/index.ts#L32-L40

How do I add a subdocument's data to a parent document (using mongoose)?

I am creating a MERN app and have a series of mongoose schema that are connected.
The hierarchy goes: Program -> Workout -> Exercise -> Set
Here is the model code for each Schema:
Program Schema
const programSchema = mongoose.Schema({
program_name:{
type: String,
},
workouts:[{
type: mongoose.Types.ObjectId,
ref: 'Workout'
}]
Workout Schema
const workoutSchema = mongoose.Schema({
workout_name:{
type:String
},
exercises: [{
type: mongoose.Types.ObjectId,
ref: 'Exercise'
}]
Exercise Schema
const exerciseSchema = mongoose.Schema({
exercise_name:{
type:String
},
notes:{
type:String
},
sets:[{
type: mongoose.Types.ObjectId,
ref: 'Set'
}]
Set Schema
const setSchema = mongoose.Schema({
weight:{
type: String
},
repetitions:{
type: String
},
rpe:{
type: String
}
My question is, now that they are all separate. How do I link a specific Set to a Exercise? or a specific Exercise to a Workout? etc. How do I reference them to each other so that I can create a whole program with various workouts, and each workout having various exercises, etc.
I would appreciate any wisdom. Thank you
For more info, here are the controllers.
Program Controller (CREATE NEW PROGRAM)
const createProgram = async (req, res) => {
//const {program_name, workouts} = req.body
try {
const program = new Program(req.body) // create a new program with the information requested
await program.save() // save it to database
res.status(201).send(program) // send it back to user
} catch (e) {
res.status(500).send(e)
}
WORKOUT CONTROLLER (CREATE NEW WORKOUT)
const createWorkout = async (req, res) => {
const {workout_name} = req.body
try {
const workout = await new Workout({
workout_name
})
await workout.save()
res.status(201).send(workout)
} catch(e) {
}
EXERCISE CONTROLLER (CREATE NEW EXERCISE)
const createExercise = async (req, res) => {
const { exercise_name='', notes='', sets } = req.body
try {
const exercise = await new Exercise({
exercise_name,
notes,
sets
})
await exercise.save()
res.status(201).send(exercise)
} catch (e) {
console.log(e)
}
SET CONTROLLER (CREATE NEW SET)
const createSet = async (req, res) => {
const {repetitions='', weight='', rpe=''} = req.body
try {
const set = await new Set({
weight,
repetitions,
rpe
})
await set.save()
res.status(201).send(set)
} catch (e) {
res.status(500).send(e)
}
The way I do it is on save I add the id to the attributed array. So i'll give you an example for one of your Routers then hopefully you can understand enough to do the rest.
For workouts you want to add it to a program when it's created. so when you create it, just add the id to the program you want to add it to.
Like so:
const {workout_name} = req.body
try {
const newWorkout = await Workout.create({
workout_name
})
Program.updateOne(
{ _id: req.params.ProgramId },
{ $addToSet: { workouts: newWorkout._id }},
)
res.status(201).send(workout)
} catch(e) {
}
So basically after creating your workout, you add that workout ID to the workouts array of the parent object. You would do the same for the rest of your Routers.

Mongoose getters are either not working the way I want or I'm misunderstanding what they are

I created some sample code to demonstrate my issue on a smaller scale. From my understanding, a getter function will not affect anything on my database, but when I want to make a get request to view items on my database, it will change the value to whatever is returned only when the data is displayed. However, when I make my get request to view items on my database, the item I am shown is exactly how it was saved. I'm not sure if I'm misunderstanding what a getter function is, or if my syntax is just incorrect somewhere.
Here is my main server:
const express = require('express')
const mongoose = require('mongoose')
// Linking my model
const User = require('./User')
// Initializing express
const app = express()
const PORT = 9999
app.use(express.json())
// Connecting to mongodb
const connectDB = async () => {
try {
await mongoose.connect('mongodb://localhost/testdatabase', {
useUnifiedTopology: true,
useNewUrlParser: true
})
console.log('Connected')
} catch (error) {
console.log('Failed to connect')
}
}
connectDB()
// Creates a new user
app.post('/user/create', async (req, res) => {
await User.create({
name: 'John Cena',
password: 'somepassword'
})
return res.json('User created')
})
// Allows me to view all my users
app.get('/user/view', async (req, res) => {
const findUser = await User.find()
return res.json(findUser)
})
// Running my server
app.listen(PORT, () => {
console.log(`Listening on localhost:${PORT}...`)
})
Here is my model:
const mongoose = require('mongoose')
// My setter - initialPassword is 'somepassword'
// This seems to work properly, in my database the password is changed to 'everyone has the same password here'
const autoChangePassword = (initialPassword) => {
console.log(initialPassword)
return 'everyone has the same password here'
}
// My getter - changedPassword should be 'everyone has the same password here' I think
// The console.log doesn't even run
const passwordReveal = (changedPassword) => {
console.log(changedPassword)
return 'fakehash1234'
}
// Creating my model
const UserSchema = mongoose.Schema({
name: {
type: String
},
password: {
type: String,
set: autoChangePassword,
get: passwordReveal
}
})
// Exporting my model
const model = mongoose.model('user', UserSchema)
module.exports = model
Not sure if it would help anyone since I found my answer on another StackOverflow post, but the issue was I had to set getters to true when converting back to JSON:
// Creating my model
const UserSchema = mongoose.Schema({
name: {
type: String
},
password: {
type: String,
set: autoChangePassword,
get: passwordReveal
}
}, {
toJSON: { getters: true }
})
Any similar problems can be solved by adding some combination of the following:
{
toJSON: {
getters: true,
setters: true
},
toObject: {
getters: true,
setters: true
}
}

How to properly use jasmine-marbles to test multiple actions in ofType

I have an Effect that is called each time it recives an action of more than one "kind"
myEffect.effect.ts
someEffect$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.actionOne, fromActions.actionTwo),
exhaustMap(() => {
return this.myService.getSomeDataViaHTTP().pipe(
map((data) =>
fromActions.successAction({ payload: data})
),
catchError((err) =>
ObservableOf(fromActions.failAction({ payload: err }))
)
);
})
)
);
in my test I tried to "simulate the two different actions but I always end up with an error, while if I try with one single action it works perfectly
The Before Each part
describe('MyEffect', () => {
let actions$: Observable<Action>;
let effects: MyEffect;
let userServiceSpy: jasmine.SpyObj<MyService>;
const data = {
// Some data structure
};
beforeEach(() => {
const spy = jasmine.createSpyObj('MyService', [
'getSomeDataViaHTTP',
]);
TestBed.configureTestingModule({
providers: [
MyEffect,
provideMockActions(() => actions$),
{
provide: MyService,
useValue: spy,
},
],
});
effects = TestBed.get(MyEffect);
userServiceSpy = TestBed.get(MyService);
});
This works perfectly
it('should return successActionsuccessAction', () => {
const action = actionOne();
const outcome = successAction({ payload: data });
actions$ = hot('-a', { a: action });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
This doesn't work
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
const outcome = successAction({ payload: data });
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a-a', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
There are two problems in this code.
It suggests that getSomeDataViaHTTP returns two values. This is wrong, the response is no different from your first example: '-a|'
It expects the second successAction to appear after 40 ms (--b--b, count the number of dashes). This is not correct, because actionTwo happens after 20 ms (-a-a) and response takes another 10 ms (-a). So the first successAction is after 20ms (10+10), the second is after 30ms (20+10). The marble is: '--b-b'.
Input actions : -a -a
1st http response : -a
2nd http response : -a
Output actions : --b -b
The working code:
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const outcome = successAction({ payload: data });
const expected = cold('--b-b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
Marble testing is cool but it involves some black magic you should prepare for. I'd very much recommend you to carefully read this excellent article to have a deeper understanding of the subject.