redux-toolkit InitialState not changing - redux-toolkit

My problem is that initialState from slice.js not changing, when I console.log store(using UseSelector) I see that state.list empty and did not changed.I'm trying to catch data from GET endpoint, endpoint is working.
store.js
import { configureStore } from '#reduxjs/toolkit';
import shopReducer from '../connection/shopSlice';
export const store = configureStore({
reducer: {
shop: shopReducer,
},
devTools: true
});
slice.js
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import axios from 'axios';
export const getProducts = createAsyncThunk(
'shop/getProducts',
async () => {
const response = await axios.get('http://localhost:3000/products');
return response.data;
}
);
export const listOfProducts = createSlice({
name: 'shop',
initialState: {
list: [],
status: 'idle',
error: null,
},
reducers: {
addProduct: {
reducer: (state, action) => {
state.list.push(action.payload);
},
prepare(value) {
return {
payload: {
key: value.id,
value: value,
},
};
},
},
},
extraReducers: {
[getProducts.pending]: (state, action) => {
state.status = 'loading';
},
[getProducts.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.list.push(...action.payload);
},
[getProducts.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
},
},
});
export const { addProduct } = listOfProducts.actions;
export default listOfProducts.reducer;
component with console.log
import React from 'react';
import common from './common.module.scss';
import shopCards from './shopCards.module.scss';
import { useSelector } from "react-redux";
const ShopCards = () => {
console.log(useSelector(state=>state))
return (
<div>
</div>
);
};
export default ShopCards;

The issue is that you are not dispatching the getProducts at all, you should dispatch this action and get the state with useSelector(state => state.shop) to select the proper reducer state. Try to change your code to the following:
import React from 'react';
import common from './common.module.scss';
import shopCards from './shopCards.module.scss';
// Don't forget to change the path
import { getProducts } from './path/to/reducer'
import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
const ShopCards = () => {
const products = useSelector((state) => {state.shop});
useEffect(() => {
// dispatch the action on first render
useDispatch(getProducts());
}, []);
useEffect(() => {
// print the products if the fetch to backend was made successfully
console.log(products);
}, [products]);
return (
<div>
</div>
);
};
export default ShopCards;
Other thing, in your createAsyncThunk you are returning response.data, so to fulfill properly the state, your api response should looks like this:
{
// you must return a JSON with data key who has an array of your products
// the content of product was just an example, so ignore it
data: [{id: 1, product: "foo"}]
}

Related

Can't access Pinia changed state in Prisma API call in a Nuxt 3 app

Somehow I when I make an API call using Prisma I can access only the default state:
import dbClient from '~/server/utils';
import { useTextStore } from '~/pinia/text'
export default defineEventHandler(async (event) => {
const { id } = event.context.params
const text = useTextStore()
const updatedText = await dbClient.opus.update({
where: {
id: parseInt(id),
},
data: {
content: text.content
},
});
return {
statusCode: 200,
body: text.content
}
})
Here's the Pinia store code:
import { defineStore } from 'pinia'
export const useTextStore = defineStore({
id: 'text',
state: () => {
return {
editor:'Введите текст',
}
},
actions: {
updateText(newContent) {
this.editor = newContent
}
},
getters: {
content: state => state.editor,
},
})
The state changes are shared across components and pages but can't get through to the eventHandler. Is it Nuxt 3 or some other mistake I should look into?

unstable_getServerSession to secure apis (nextAuth)

i need to secure the API so that only authorized user can access them. I followed the documentation in this link https://next-auth.js.org/tutorials/securing-pages-and-api-routes#securing-api-routes but apparently I am not retrieving the session.
I am able to console.log the authOptions but if I try to console log the session (and I am logged in), it logs "null"
This is the code
pages/api/profile.js
import prisma from "../../../lib/prisma";
import { unstable_getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
export default async function handler(req, res) {
const session = await unstable_getServerSession(req, res, authOptions);
console.log("SESSION", session); // this logs "null"
if (!session) {
return res.status(401).json("Not authorized");
}
try {
const user = await prisma.user.findUnique({
where: { email: session.user.email },
});
return res.status(200).json(user);
} catch (error) {
console.error(error);
return res
.status(503)
.json(
"Our server is not able to process the request at the moment, please try again later!"
);
}
pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import CognitoProvider from "next-auth/providers/cognito";
import prisma from "../../../lib/prisma";
export const authOptions = {
providers: [
CognitoProvider({
clientId: process.env.CLIENTID_NEXTAUTH,
issuer: process.env.COGNITO_ISSUER,
clientSecret: process.env.CLIENTSECRET_NEXTAUTH,
}),
],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60,
updateAge: 24 * 60 * 60,
},
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
const user = await prisma.user.findUnique({
where: { email: session?.user?.email },
});
if (!user) throw new Error("User not found in the database.");
const mySession = {
...session,
accessToken: token.accessToken,
email: user.email,
};
return mySession;
},
},
};
export default NextAuth(authOptions);
pages/dashboard/index.js
import axios from "axios";
import React, { useState } from "react";
import { getSession, useSession } from "next-auth/react";
const Dashboard = (props) => {
let { data: session, status } = useSession();
if (status === "loading") {
return <p>Loading...</p>;
}
if (status === "unauthenticated") {
window.location.reload();
}
return (
<p>
{props.userInfo.name}
</p>
);
};
export default Dashboard;
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
console.log("SESSION IN INDEX", session); // this logs the session
const userInfo = await axios.get(
`${process.env.BASE_URL}/api/profile?email=${session.email}`
);
return {
props: {
session,
userInfo: userInfo.data ? userInfo.data : null,
},
};
}
so when I login, I can see the SESSION in INDEX but when I hit the api/profile, the session from unstable_getServerSession is null, so I canno see nothing in the dashboard
resolved:
when calling the api you need to pass the headers, for example in the dashboard/index.js
const userInfo = await axios.get(
`${process.env.BASE_URL}/api/profiles/profile?email=${session.email}`,
{
withCredentials: true,
headers: {
Cookie: context.req.headers.cookie,
},
}
);
while in the API endpoint
import { getServerSession, getSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
export default async function handler(req, res) {
const session = await getServerSession(req, res, authOptions);
console.log("SESSION", session);
//your code
}

delete item from apiCall need reload page to deleted from client

i use redux toolkit with react native and mongodb (mongoose)
i delete item and it successfully deleted from db
but not in client and need to reload page
todoSlice :
import {createSlice} from '#reduxjs/toolkit';
export const todoSlice = createSlice({
name: 'todos',
initialState: {
todos: [],
pending: null,
error: null,
},
reducers: {
deleteTodo: (state, action) => {
return state
},
},
});
export const {deleteTodo} = todoSlice.actions;
export default todoSlice.reducer;
apiCall:
import axios from 'axios';
import {deleteTodo} from './todoSlice';
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
dispatch(deleteTodo());
} catch (err) {
console.log(err);
}
};
main :
const {todo} = useSelector(state => state);
const dispatch = useDispatch();
const {todos} = todo;
useEffect(() => {
getTodos(dispatch);
}, []);
const handleDelete = id => {
deleteOneTodo(id, dispatch);
};
you have to implement deleteTodo inside your todoSlice in order to remove the deleted id from your local state,
...
export const todoSlice = createSlice({
name: 'todos',
initialState: {
todos: [],
pending: null,
error: null,
},
reducers: {
deleteTodo: (state, action) => {
return state.filter((todo)=>todo.id!==action.payload.id);
},
},
});
...
and of course you have to pass the payload with the id of the todo you want to remove
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
dispatch(deleteTodo({id:id}));
} catch (err) {
console.log(err);
}
};
if you still have doubts you can follow this tutorial: https://www.youtube.com/watch?v=fiesH6WU63I
i just call 'getTodos' inside 'deleteOneTodo'
and delete 'deleteTodo' from reducer
i hope its a good practice
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
// i add this line =>
getTodos(dispatch);
} catch (err) {
console.log(err);
}
};

How to write Nestjs unit tests for #Injectable() mongodb service

Can someone please guide me. I'm learning Nestjs and doing a small project, and I'm not able to get the unit test working for a controller and service which has dependency on the database.module. How do I go about mocking the database.module in the product.service.ts? Any help will be highly appreciated.
database.module.ts
try {
const client = await MongoClient.connect(process.env.MONGODB, { useNewUrlParser: true, useUnifiedTopology: true });
return client.db('pokemonq')
} catch (e) {
console.log(e);
throw e;
}
};
#Module({
imports: [],
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: setupDbConnection
},
],
exports: ['DATABASE_CONNECTION'],
})
export class DatabaseModule {}
product.service.ts
#Injectable()
export class ProductService {
protected readonly appConfigObj: EnvConfig;
constructor(
private readonly appConfigService: AppConfigService,
#Inject('DATABASE_CONNECTION') => **How to mock this injection?**
private db: Db,
) {
this.appConfigObj = this.appConfigService.appConfigObject;
}
async searchBy (){}
async findBy (){}
}
product.service.spec.ts
describe('ProductService', () => {
let service: ProductService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
ConfigService,
DatabaseModule,
AppConfigService,
ProductService,
{
provide: DATABASE_CONNECTION,
useFactory: () => {}
}
],
}).compile();
service = module.get< ProductService >(ProductService);
});
afterAll(() => jest.restoreAllMocks());
}
product.controller.spec.ts
describe('ProductController', () => {
let app: TestingModule;
let ProductController: ProductController;
let ProductService: ProductService;
const response = {
send: (body?: any) => {},
status: (code: number) => response,
json: (body?: any) => response
}
beforeEach(async () => {
app = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [appConfig],
isGlobal: true,
expandVariables: true
}),
ProductModule,
],
providers: [
AppConfigService,
ProductService,
],
controllers: [ProductController]
}).compile();
productController = app.get< ProductController >(ProductController);
productService = app.get< ProductService >(ProductService);
});
afterAll(() => jest.restoreAllMocks());
}
Anything that is not being tested directly in a unit test should theoretically be mocked. In this case, you have two dependencies, AppConfigService adn DATABASE_CONNECTION. You're unit test should provide mock objects that look like the injected dependencies, but have defined and easy to modify behavior. In this case, something like this may be what you're looking for
beforeEach(async () => {
const modRef = await Test.createTestingModule({
providers: [
ProductService,
{
provide: AppConfigService,
useValue: {
appConfigObject: mockConfigObject
}
},
{
provide: 'DATABASE_CONNECTION',
useValue: {
<databaseMethod>: jest.fn()
}
]
}).compile();
// assuming these are defined in the top level describe
prodService = modRef.get(ProductionService);
conn = modRef.get('DATABASE_CONNECTION');
config = modRef.get(AppConfigService);
});
In your controller test, you shouldn't worry about mocking anything other than the ProdctService.
If you need more help there's a large repository of examples here
Edit 9/04/2020
Mocking chained methods is a major pain point when working with things like Mongo. There's a few ways you can go about it, but the easiest is probably to create a mock object like
const mockModel = {
find: jest.fn().mockReturnThis(),
update: jest.fn().mockReturnThis(),
collation: jest.fn().mockReturnThis(),
...etc
}
And on the last call in the chain, make it return the expected outcome so your service can keep running the rest of the code. This would mean if you have a call like
const value = model.find().collation().skip().limit().exec()
you would need to set the exec() method to return the value you expect it to, probably using something like
jest.spyOn(mockModel, 'exec').mockResolvedValueOnce(queryReturn);
I am also exploring using native Mongodb with NestJS. Below is my working test for cron job service updating value in db.
src/cron/cron.service.ts
import { Inject, Injectable } from '#nestjs/common';
import { Cron, CronExpression } from '#nestjs/schedule';
import { Db } from 'mongodb';
import { Order } from 'src/interfaces/order.interface';
#Injectable()
export class CronService {
constructor(
#Inject('DATABASE_CONNECTION')
private db: Db,
) {}
#Cron(CronExpression.EVERY_30_SECONDS)
async confirmOrderEveryMinute() {
console.log('Every 30 seconds');
await this.db
.collection<Order>('orders')
.updateMany(
{
status: 'confirmed',
updatedAt: {
$lte: new Date(new Date().getTime() - 30 * 1000),
},
},
{ $set: { status: 'delivered' } },
)
.then((res) => console.log('Orders delivered...', res.modifiedCount));
}
}
src/cron/cron.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { Db } from 'mongodb';
import { CronService } from './cron.service';
describe('CronService', () => {
let service: CronService;
let connection: Db;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CronService,
{
provide: 'DATABASE_CONNECTION',
useFactory: () => ({
db: Db,
collection: jest.fn().mockReturnThis(),
updateMany: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
}),
},
],
}).compile();
service = module.get<CronService>(CronService);
connection = module.get('DATABASE_CONNECTION');
});
it('should be defined', async () => {
expect(service).toBeDefined();
});
it('should confirmOrderEveryMinute', async () => {
await service.confirmOrderEveryMinute();
expect(connection.collection('orders').updateMany).toHaveBeenCalled();
});
});

Want to fetch data from firestore to my ionic 4 app

I was able to upload some data to my firestore account, but i can't fetch or read the data. I'm new and will appreciate if i am guided like a newbie.
ngOnInit() {
this.bookingservice.read_AcBookings().then(data =>
this.booking = data.map(e => {
return {
id: e.payload.doc.id,
isEdit: false,
// tslint:disable-next-line: no-string-literal
firstname: e.payload.doc.data()['firstname'],
// tslint:disable-next-line: no-string-literal
lastname: e.payload.doc.data()['lastname'],
// tslint:disable-next-line: no-string-literal
phonenumber: e.payload.doc.data()['phonenumber'],
// tslint:disable-next-line: no-string-literal
address: e.payload.doc.data()['address'],
// tslint:disable-next-line: no-string-literal
location: e.payload.doc.data()['location'],
// tslint:disable-next-line: no-string-literal
date: e.payload.doc.data()['date'],
// tslint:disable-next-line: no-string-literal
servicebooked: e.payload.doc.data()['servicebooked'],
};
}));
console.log(this.booking);
}
This is the service
read_AppliancesBookings() {
return new Promise<any>((resolve, reject) => {
this.afAuth.user.subscribe(currentUser => {
if (currentUser) {
this.snapshotChangesSubscription = this.firestore.collection('Bookings').doc(currentUser.uid).collection('Appliances Bookings')
.snapshotChanges();
resolve(this.snapshotChangesSubscription);
}
});
});
}
There are a few fundamental mistakes in your bookingservice, so I just rewrote it for you. Hopefully you can learn what was wrong by looking at this code.
import { Injectable } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/auth';
import { AngularFirestore } from '#angular/fire/firestore';
import { reject } from 'q';
#Injectable({
providedIn: 'root'
})
export class bookingservice {
constructor(public afAuth: AngularFireAuth, public firestore: AngularFirestore) { }
userId = this.afAuth.auth.currentUser.uid;
read_AppliancesBookings() {
return new Promise((resolve, reject) => {
this.firestore
.doc<any>(`Bookings/${userId}`)
.valueChanges()
.subscribe(doc => {
resolve(doc);
});
}).then((result) => {
return result;
});
}
}
Let me know if that works.