why do we need thunk.withExtraArgument? - node.js

I currently have an issue with my code so while I am debugging i am trying to understand which approach shall I take in order to solve the issue.
I am using google oauth with passport.js. from client I have an action to fetch the authentication.
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:3000/api",
timeout: 3000,
});
export const fetchUser = () => async (dispatch, getState, api) => {
try {
await axiosInstance.get("/auth/current_user").then((res) => {
dispatch({ type: FETCH_USER, payload: res.data });
});
} catch (e) {
console.log(e.message);
}
};
here is the client-side store set up:
const store = createStore(
reducers,
window.INITIAL_STATE, //
applyMiddleware(thunk)
);
and this is the server side store set up:
export default (req) => {
const store = createStore(reducers, {}, applyMiddleware(thunk));
console.log("store from servre", store);
return store;
};
Another approach would be instead of defining axiosInstance in action, i define it when I create the client side store and pass it to thunk.withExtraArgument iike this.
const axiosInstance = axios.create({
baseURL: "/",
});
const store = createStore(
reducers,
window.INITIAL_STATE,
applyMiddleware(thunk.withExtraArgument(axiosInstance))
);
I changed the action accordingly:
export const fetchUser = () => async (dispatch, getState, api) => {
try {
await api.get("/auth/current_user").then((res) => {
dispatch({ type: FETCH_USER, payload: res.data });
});
} catch (e) {
console.log(e.message);
}
};
My question which method should I use. CUrrently I have issue in both methods :) . But if i know which way is correct then I will solely focus on that method.
Thank You

I will suggest you go with with creating an instance of axios in a file and importing it whereever you need it, instead of adding it as an argument to thunk
api.js
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:3000/api",
timeout: 3000,
});
export default axiosInstance;
actions.js
import api from '/path/to/api';
export const fetchUser = () => async (dispatch, getState) => {
try {
await api.get("/auth/current_user").then((res) => {
dispatch({ type: FETCH_USER, payload: res.data });
});
} catch (e) {
console.log(e.message);
}
};
The advantage of the above solution is that you can import and use the instance for making direct API calls too in your components for which you do not need to dispatch actions to the reducers.

Related

Can't fetch my data from my backend(NodeJS) to my react native app

I have tried to get my json data but it's not working. It works to get the data on postman and on chrome but when I try to fetch the data it comes back as null. I'm using apisauce.
any ideas? I'm new to react native :)
const [listings, setListings] = useState([])
useEffect(() => {
loadListings()
}, []);
const loadListings = async () => {
const response = await listingsApi.getListings()
console.log(response.data, "response.data")
setListings(response.data)
}
import { create } from "apisauce";
const apiClient = create({
baseURL: "http://127.0.0.1:9000/api",
});
export default apiClient;
import client from "./client";
const endpoint = "/listings";
const getListings = () => client.get(endpoint);
export default {
getListings,
};
You can call loadListing like that (because loadListing is asynchronous function)
useEffect(() => {
loadListings().then(() => {}).catch(() => {})
},[])
or can be called using IIFE
useEffect(() => {
(async() => {
await loadListings();
})()
},[])

why i cant get an axios response in react?

so I'm having a problem getting data from my server to my front-end using axios.
as you can see in this picture I'm getting a response for the GET method for users/users.
this is my showUsers function
const showUsers = async (req, res) => {
await User.find({})
.then((user) => {
res.status(200).json(user);
})
.catch((error) => {
res.status(400).send(error);
});
};
this is my axios api export
import axios from "axios";
export default axios.create({
baseUrl: "http://localhost:8080/users",
});
and this is my useEffect
import api from "../api/users";
import { useState, useEffect } from "react";
export const LogIn = (props) => {
const { setIsNewMember } = props;
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await api.get("/users");
setUsers(response.data);
} catch (err) {
if (err.response) {
console.log(err.response.data);
console.log(err.response.status);
console.log(err.response.headers);
} else {
console.log(`Error: ${err.message}`);
}
}
};
fetchUsers();
}, []);
I'm getting this error on the frontend
so although I'm getting it from the postman and other services I'm not getting it on the front.
any idea why is that happening?

How to use Redux to dispatch data to the backend (and consequently mongoDB)?

I recently created a simple MERN application that is supposed to use a form to send data to the backend using Redux to maintain state management. I'm new to Redux (as you will see in my code) and I believe I must have messed up the dispatching.
Below are the functions in my Form component:
const [landlordData, setLandlordData] = useState({name: '', type: '', rating: '', details: ''});
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
console.log(landlordData);
dispatch(createLandlord(landlordData));
}
Which console log the data from the form normally. When I submit though the new entry in the MongoDB only includes the time created and the UUID of the entry due to the Schema of the database:
import mongoose from 'mongoose';
const landlordSchema = mongoose.Schema({
name: String,
type: String,
rating: Number,
details: String,
createdAt: {
type: Date,
default: new Date()
}
});
var landlordDetails = mongoose.model('Landlords', landlordSchema);
export default landlordDetails;
To provide more context on the backend operations here is the controller script I made:
import landlordDetails from '../models/landlords.js';
export const getLandlords = async (req, res) => {
try {
const getDetails = await landlordDetails.find();
console.log(getDetails);
res.status(200).json(getDetails);
} catch (error) {
res.status(404).json({ message: error.message });
}
}
export const createLandlords = async (req, res) => {
const details = req.body;
const newLandlord = new landlordDetails(details);
try {
await newLandlord.save();
res.status(201).json(newLandlord);
console.log("New landlord added!");
} catch (error) {
res.status(409).json({ message: error.message })
}
}
Please let me know if any more information is needed or if I am completely oblivious to something obvious. Thank you.
EDIT: To provide more context, here are my api calls and my action script:
API:
import axios from 'axios';
const url = 'http://localhost:5000/landlords';
export const fetchLandlords = () => axios.get(url);
export const createLandlord = (landlordData) => axios.post(url, landlordData);
Actions JS file:
import * as api from '../api/index.js';
//Action creators
export const getLandlords = () => async (dispatch) => {
try {
const { data } = await api.fetchLandlords();
dispatch({ type: 'FETCH_ALL', payload: data });
} catch (error) {
console.log(error.message);
}
};
export const createLandlord = (landlord) => async (dispatch) => {
try {
const { data } = await api.createLandlord(landlord);
dispatch({ type: 'CREATE', payload: data });
} catch (error){
console.log(error);
}
};
When I click the submit button, a new database entry is made with the createdAt field but nothing else.

Test ApolloClient API with Jest in Vue3 (Composition API)

I am using Vue3 (typescript) with Composition API for my application. I am using ApolloClient grapghql for API calls. I have created a separate service file for API calls. (PFB files)
Service file
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client/core"
import { gql } from "#apollo/client/core"
import fetch from 'cross-fetch';
const httpLink = new HttpLink({
uri: process.env.VUE_APP_BACKEND_GRAPHQL_URI,
fetch
})
const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
})
export const getAplloAPIdata = async (reqQuery: any) => {
const query = gql `${reqQuery}`
try {
return await apolloClient.query({ query })
}catch {
console.log('API error')
}
}
Home.vue
setup() {
const threatList = ref([])
const threat = ref(null)
// get all threats
const getThreats = async () => {
const getThreatsQuery = `
query {
threats {
short_description
threat_level
}
}
`
try {
const result = await getAplloAPIdata(getThreatsQuery)
if (result) {
threatList.value = result.data.threats
}
} catch {
console.log('Error receiving threats data')
}
}
Can you please tell me how can I write test cases to mock this API in jest? Thank you!
I would mock getAplloAPIdata to return mock data, and verify that data in your test. The key is to make sure the mock path is the same as that imported in your component:
// Home.vue
import { getAplloAPIdata } from '#/service'
/*...*/
// Home.spec.js
jest.mock('#/service', () => {
return {
getAplloAPIdata: () => ({
data: {
threats: [{ id: 123456 }]
}
})
}
})
describe('Home.vue', () => {
it('gets threats', async () => {
const wrapper = shallowMount(Home)
await wrapper.vm.getThreats()
expect(wrapper.vm.threatList).toContainEqual({ id: 123456 })
})
})
GitHub demo

How to mock AxiosInstance with jest [duplicate]

I'm using jest and axios-mock-adapter to test axios API calls in redux async action creators.
I can't make them work when I'm using a axios instance that was created with axios.create() as such:
import axios from 'axios';
const { REACT_APP_BASE_URL } = process.env;
export const ajax = axios.create({
baseURL: REACT_APP_BASE_URL,
});
which I would consume it in my async action creator like:
import { ajax } from '../../api/Ajax'
export function reportGet(data) {
return async (dispatch, getState) => {
dispatch({ type: REQUEST_TRANSACTION_DATA })
try {
const result = await ajax.post(
END_POINT_MERCHANT_TRANSACTIONS_GET,
data,
)
dispatch({ type: RECEIVE_TRANSACTION_DATA, data: result.data })
return result.data
} catch (e) {
throw new Error(e);
}
}
}
Here is my test file:
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
let mock = new MockAdapter(axios)
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
And I only get one action Received: [{"type": "REQUEST_TRANSACTION_DATA"}] because there was an error with the ajax.post.
I have tried many ways to mock the axios.create to no avail without really knowing what I'm doing..Any Help is appreciated.
OK I got it. Here is how I fixed it! I ended up doing without any mocking libraries for axios!
Create a mock for axios in src/__mocks__:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
Then in your test file, the gist would look like:
import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')
// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore()
const mockData = {
'data': 123
}
/**
* SETUP
* This is where you override the 'post' method of your mocked axios and return
* mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
* mirrors the actual API call, in this case, the 'reportGet'
*/
mockAxios.post.mockImplementationOnce(() =>
Promise.resolve({ data: mockData }),
)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
// work
await store.dispatch(reportGet())
// assertions / expects
expect(store.getActions()).toEqual(expectedActions)
expect(mockAxios.post).toHaveBeenCalledTimes(1)
})
If you need to create Jest test which mocks the axios with create in a specific test (and don't need the mock axios for all test cases, as mentioned in other answers) you could also use:
const axios = require("axios");
jest.mock("axios");
beforeAll(() => {
axios.create.mockReturnThis();
});
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Here is the link to the same example of Axios mocking in Jest without create. The difference is to add axios.create.mockReturnThis()
here is my mock for axios
export default {
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
create: jest.fn(function () {
return {
interceptors:{
request : {
use: jest.fn(() => Promise.resolve({ data: {} })),
}
},
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
}
}),
};
In your mockAdapter, you're mocking the wrong instance. You should have mocked ajax instead. like this, const mock = MockAdapter(ajax)
This is because you are now not mocking the axios instance but rather the ajax because it's the one you're using to send the request, ie, you created an axios instance called ajax when you did export const ajax = axios.create...so since you're doing const result = await ajax.post in your code, its that ajax instance of axios that should be mocked, not axios in that case.
I have another solution.
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
// import axios from 'axios'
import { ajax } from '../../api/Ajax' // axios instance
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
// let mock = new MockAdapter(axios)
let mock = new MockAdapter(ajax) // this here need to mock axios instance
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
another method: add this file to src/__mocks__ folder
import { AxiosStatic } from 'axios';
const axiosMock = jest.createMockFromModule<AxiosStatic>('axios');
axiosMock.create = jest.fn(() => axiosMock);
export default axiosMock;
The following code works!
jest.mock("axios", () => {
return {
create: jest.fn(() => axios),
post: jest.fn(() => Promise.resolve()),
};
});

Resources