ReactJS: Axios' async request is returning 'undefined' - javascript

I'm doing a little project using the movie DB API is ReactJs, and I have problems to use async functions, i'm making the api calls in many parts of projects an worked in all of them, but now it's not working.
My axios config:
export default axios.create({
baseURL: 'https://api.themoviedb.org/3/',
params: {
api_key: process.env.REACT_APP_API,
},
});
In my container I have this
const { setMenuSelected } = useContext(MovieContext);
const [movie, setMovie] = useState({})
const [recommendations, setRecommendations] = useState([]);
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
animateScroll.scrollToTop({ smooth: true });
setMenuSelected('');
getMovieRecommendations(setRecommendations, match.params.id, setIsLoading);
setMovie(getMovieDetails(match.params.id, setMovie));
}, [match.params.id, recommendations, isLoading]);
I'm fetching the data in other file just to organize the project
export const getMovieRecommendations = async (
setRecommendations,
movieId,
setIsLoading) => {
const res = await api.get(`/movie/${movieId}/recommendations`);
setRecommendations(res.results);
setIsLoading(false);
}
And to render the movie list I'm passing the recommendations to my component that only use map to render each movie,
<MovieList header={'Recommentadion'} movieList={recommendations} />
But the app allways crash saying "Cannot read property 'map' of undefined", i looked in the network properties in the chrome and my request is finishing with no erros.
Update:
return of console.log(JSON.stringify(res)) I removed the results itens so it wouldn't get too big
{
"data":{
"page":1,
"results":[
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
],
"total_pages":2,
"total_results":40
},
"status":200,
"statusText":"",
"headers":{
"cache-control":"public, max-age=14400",
"content-type":"application/json;charset=utf-8",
"etag":"W/\"56ca661f28e43dc3dd2ce41816b517c5\""
},
"config":{
"transformRequest":{
},
"transformResponse":{
},
"timeout":0,
"xsrfCookieName":"XSRF-TOKEN",
"xsrfHeaderName":"X-XSRF-TOKEN",
"maxContentLength":-1,
"headers":{
"Accept":"application/json, text/plain, */*"
},
"method":"get",
"baseURL":"https://api.themoviedb.org/3/",
"params":{
"api_key":"fe5b485ed12153ca357c3275cfad6c5c"
},
"url":"https://api.themoviedb.org/3/movie/399121/recommendations"
},
"request":{
}
}

Try doing:
const res = await api.get(`/movie/${movieId}/recommendations`);
console.log(JSON.stringify(res));
This will print the whole JSON object into a string in the console. Since you're getting undefined and no error, you are most likely accessing the JSON object incorrectly. By printing out the contents this will show you what your JSON object looks like so you can get to the 'results' property successfully.

Explain axios.create & Example
axios.create is for initial.. (config)
const axios = Axios.create({ withCredentials: false/true });
Now, create a function using axios like that:
function get(...args) {
return axios.get(...args)
}
and call as you write:
const res = await get(`/movie/${movieId}/recommendations`);

I think this help you.
you should export your axios config like this:
const APIClient = axios.create({
baseURL: 'https://api.themoviedb.org/3/',
params: {
api_key: process.env.REACT_APP_API,
}
});
export default APIClient;
and then import it to fetch data in another file:
import APIClient from "YOUR AXIOS CONFIG FILE";
export const getMovieRecommendations = async (movieId) => {
const { data } = await APIClient.get(`/movie/${movieId}/recommendations`);
return data;
}
In your container set this:
const { setMenuSelected } = useContext(MovieContext);
const [movie, setMovie] = useState({})
const [recommendations, setRecommendations] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const getList=async(movieId)=>{
const res = await getMovieRecommendations(movieId);
setRecommendations(res.results);
setIsLoading(false);
}
useEffect(() => {
animateScroll.scrollToTop({ smooth: true });
setMenuSelected('');
getList(match.params.id);
setMovie(getMovieDetails(match.params.id, setMovie));
}, [match.params.id, recommendations, isLoading]);

Related

next.js getStaticProps Serialize issue

I'm using next.js for a project where axios fetch in getStaticProps doesnot seem to work even though the URL is serialised in configuration.I tried serializing again by passing the response to JSON.parse but still cant find a solution.
import axios from "axios";
import Qs from "qs";
My axios config code below:
const axiosTmdbApi = axios.create({
baseURL: "https://api.themoviedb.org/3",
headers: { "content-Type": "application/json/" },
paramsSerializer: {
serialize: (params) =>
Qs.stringify({ ...params, api_key: apiKey }, { arrayFormat: "brackets" }),
},
});```
**My category which is passed as a parameter to invoke getTvList or getMovieList data below:**
import axiosTmdbApi from "./axiosTmdbApi";
export const category = {
movie: "movie",
tv: "tv",
};
export const type = {
top_rated: "top_rated",
popular: "popular",
};
const tmdbApi = {
getTvList: (tvType, params) => {
const url = "tv/" + type[tvType];
return axiosTmdbApi.get(url, params);
},
getMovielist: (movieType, params) => {
const url = "movie/" + type[movieType];
return axiosTmdbApi.get(url, params);
},
};
export default tmdbApi;```
Using getStaticProps to fetch my API
import tmdbApi from "../../api/tmdbApi";
import { type, category } from "../../api/tmdbApi";
const Movies = ({ data }) => {
console.log(data);
return (
<>
<h1 className="bg-success">Movies</h1>
</>
);
};
export default Movies;
export async function getStaticProps() {
let params = {};
let response;
response = await tmdbApi.getMovielist(type.popular, {
params,
});
const data = JSON.parse(JSON.stringify(response));
return {
props: { data },
};
}```
**Error :index.js?46cb:602 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'TLSSocket'
--- property '_httpMessage' closes the circle **
Try adding console.log and see what values are being handled at each stage. Instead of const data = JSON.parse(JSON.stringify(response)), you should be doing const data = response.data.
and change return statement to
return {
props: { data: data || [] },
};

React.Js and Typescript how to read data from JSON?

everyone.
I am using MongoDB with mongoose and React.Js.
I have a page where the user can display a post.
I am sending fetch request to the backend where I am getting this json data as response.
{
"post": {
"_id": "62cd5b5ef2a39582f96ad514",
"title": "asdadsad",
"description": "sdasdasdasda",
"imageURL": "image 1",
"creator_id": "62cd5b1bf2a39582f96ad500",
"createdAt": "2022-07-12T11:30:38.255Z",
"updatedAt": "2022-07-12T11:30:38.255Z",
"__v": 0,
"id": "62cd5b5ef2a39582f96ad514"
}
}
And on the frontend I am using Fetch API, to get this data, what I am trying to do is I want to be able to read every single key and value from the JSON response as I want to use this data to display title, content etc...
const { isLoading, error, sendRequest, clearError } = useHttpRequest();
const [getPost, setPost] = useState([]);
const userID = useParams().id;
useEffect(() => {
const fetchPosts = async () => {
try {
const url: string = `http://localhost:8000/api/posts/${userID}`;
const responseData = await sendRequest(url);
console.log(responseData);
setPost(responseData);
} catch (err) { }}
fetchPosts();
}, [sendRequest]);
Now I had tried using the getPost.map(.......), however I got error that said getPost.map is not a function event when I did setPost(responseData.post) I got the same error.
So how can I access different data in the JSON response ?
In case this helps here is my sendRequest function.
and this is my sendRequest that is located in totaly different file
const useHttpRequest = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const activeHttpRequests: any = useRef([]);
const sendRequest = useCallback( async (url: string, method: string = 'GET', body: any = null, headers: {} = {}) => {
setIsLoading(true);
const httpAbort = new AbortController();
activeHttpRequests.current.push(httpAbort);
try {
const response = await fetch(url, {
method: method,
headers: headers,
body: body,
signal: httpAbort.signal //FIX FOR CROSS REQUEST SENDING
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter((requests: any) => requests !== httpAbort);
if (!response.ok){
throw new Error("Error fetch failed !");
}
setIsLoading(false);
return responseData;
} catch (err: any) {
console.log(err.message)
setError(err.message);
setIsLoading(false);
throw err;
};
}, [])
const clearError = () => {
setError(null);
}
useEffect(() => {
return () => {
activeHttpRequests.current.forEach((abortRequest: any) => abortRequest.abort()) //ABORT CURRENT REQUEST
};
}, []);
return { isLoading, error, sendRequest, clearError }
}
export default useHttpRequest
The map function is only present on arrays, but the data you gave is an Object. You can access it using subscript notation or iterate over the keys of the object.
const data = {
"post": {
"_id": "62cd5b5ef2a39582f96ad514",
"title": "asdadsad",
"description": "sdasdasdasda",
"imageURL": "image 1",
"creator_id": "62cd5b1bf2a39582f96ad500",
"createdAt": "2022-07-12T11:30:38.255Z",
"updatedAt": "2022-07-12T11:30:38.255Z",
"__v": 0,
"id": "62cd5b5ef2a39582f96ad514"
}
}
// access a single field
console.log(data.post.title) // asdadsad
// iterate over all fields in "post"
Object.keys(data.post).forEach((key, value) => console.log(`${key}: ${data.post[key]}`))
Your return Object would look something like this:
export interface Posts {
post: Post[];
}
export interface Post {
_id: string;
title: string;
description: string;
imageURL: string;
creator_id: string;
createdAt: Date;
updatedAt: Date;
__v: number;
id: string;
}
To simplify your work and make sure you get the correct data back, you should consider doing this below:
const { isLoading, error, sendRequest, clearError } = useHttpRequest();
const [getPost, setPost] = useState<Posts>([]);
const userID = useParams().id;
const fetchPosts = async () => {
try {
const url: string = `http://localhost:8000/api/posts/${userID}`;
const responseData = await sendRequest(url);
console.log(responseData);
setPost(responseData);
} catch (err) { }}
useEffect(() => {
fetchPosts();
}, [sendRequest]);
return (
<div>
<h1>get Data</h1>
{getPost.post.map((value,index) => (
<li key={`${index}-${value}`}>{value}</li>
))}
</div>
)

React.js fetch multiple endpoints of API

I am doing a React.js project. I am trying to pull data from an API that has multiple endpoints. I am having issues with creating a function that pulls all the data at once without having to do every endpoint separetly. The console.log gives an empty array and nothing gets display. The props 'films' is data from the parent and works fine. It is also from another enpoint of the same API. This is the code:
import { useEffect, useState } from "react";
import styles from './MovieDetail.module.css';
const MovieDetail = ({films}) => {
const [results, setResults] = useState([]);
const fetchApis = async () => {
const peopleApiCall = await fetch('https://www.swapi.tech/api/people/');
const planetsApiCall = await fetch('https://www.swapi.tech/api/planets/');
const starshipsApiCall = await fetch('https://www.swapi.tech/api/starships/');
const vehicleApiCall = await fetch('https://www.swapi.tech/api/vehicles/');
const speciesApiCall = await fetch('https://www.swapi.tech/api/species/');
const json = await [peopleApiCall, planetsApiCall, starshipsApiCall, vehicleApiCall, speciesApiCall].json();
setResults(json.results)
}
useEffect(() => {
fetchApis();
}, [])
console.log('results of fetchApis', results)
return (
<div className={styles.card}>
<div className={styles.container}>
<h1>{films.properties.title}</h1>
<h2>{results.people.name}</h2>
<p>{results.planets.name}</p>
</div>
</div>
);
}
export default MovieDetail;
UPDATE
I just added the post of Phil to the code and I uploaded to a codesanbox
You want to fetch and then retrieve the JSON stream from each request.
Something like this
const urls = {
people: "https://www.swapi.tech/api/people/",
planets: "https://www.swapi.tech/api/planets/",
starships: "https://www.swapi.tech/api/starships/",
vehicles: "https://www.swapi.tech/api/vehicles/",
species: "https://www.swapi.tech/api/species/"
}
// ...
const [results, setResults] = useState({});
const fetchApis = async () => {
try {
const responses = await Promise.all(Object.entries(urls).map(async ([ key, url ]) => {
const res = await fetch(url)
return [ key, (await res.json()).results ]
}))
return Object.fromEntries(responses)
} catch (err) {
console.error(err)
}
}
useEffect(() => {
fetchApis().then(setResults)
}, [])
Each URL will resolve to an array like...
[ "people", [{ uid: ... }] ]
Once all these resolve, they will become an object (via Object.fromEntries()) like
{
people: [{uid: ... }],
planets: [ ... ],
// ...
}
Take note that each property is an array so you'd need something like
<h2>{results.people[0].name}</h2>
or a loop.

How to pre-fetch data using prefetchQuery with React-Query

I am trying to pre-fetch data using react-query prefetchQuery. When I am inspecting browser DevTools network tab I can see that data that was requested for prefetchQuery is coming from the back-end but for some reason when I look into react-query DevTools it does generate the key in the cache but for some reason the Data is not there. Let me know what I am doing wrong.
import { useState, useEffect } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import axios from 'axios';
const baseURL = process.env.api;
async function getSubCategoryListByCategoryId(id) {
// await new Promise((resolve) => setTimeout(resolve, 300));
console.log(`${baseURL}/category/subcategories/${id}`);
try {
const { data } = await axios.request({
baseURL,
url: `/category/subcategories/${id}`,
method: 'get',
});
console.log('data getSubCategoryListByCategoryId index: ', data);
return data;
} catch (error) {
console.log('getSubCategoryListByCategoryId error:', error);
}
}
// const initialState = {
// };
const ProductCreate = () => {
const [values, setValues] = useState(initialState);
const queryClient = useQueryClient();
const { data, isLoading, isError, error, isFetching } = useQuery(
'categoryList',
getPosts
);
const dataList = JSON.parse(data);
useEffect(() => {
setValues({ ...values, categories: dataList });
dataList.map((item) => {
console.log('useEffect values.categories item.id: ', item._id);
queryClient.prefetchQuery(
['subCategoryListByCategoryId', item._id],
getSubCategoryListByCategoryId(item._id)
);
});
}, []);
return <h1>Hello</h1>;
};
export default ProductCreate;
The second parameter to prefetchQuery expects a function that will fetch the data, similar to the queryFn passed to useQuery.
But here, you are invoking the function, thus passing the result of it into prefetchQuery:
getSubCategoryListByCategoryId(item._id)
if you want to do that, you can manually prime the query via queryClient.setQueryData, which accepts a key and the data for that key passed to it.
otherwise, the fix is probably just:
() => getSubCategoryListByCategoryId(item._id)

Mock inner axios.create()

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()),
};
});

Categories

Resources