Map over Axios Response and append 2nd Axios Response to 1st - reactjs

I'm creating a movie search React App using axios and the TMDb API. I want to make an axios.get call to search for a movie based on query, then map over the response, and pass the movie.id for each movie into a 2nd axios call to get more details for each movie.
I have tried many approaches, but I'm confused about how I need to return the values, or if I need to use async/await at some point in the code to wait for responses. I don't really know the best way to go about this - there's got to be a simple way to do it in sequence, but the asynchronous nature of the problem makes this difficult for me.
I thought it would be simpler to make my first axios call inside my search function, and then write a second function getDetails(id) that takes a movie id and makes an axios call with it.
Ideally, if I call the getDetails function in a map, get a response, append that response to my movies array, I could easily have an array of movies with appended details to each individual movie. I could then store the entire array in state, and use a single piece of state to manage all my movie data. I know that this process is not synchronous and that axios is promise based, but is there a way to go about this sequentially or am I approaching it all wrong? I know that .push() is not working in this situation, but I have tried other methods and they have always returned undefined, a promise, or an unintended result.
const search = query => {
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
)
.then(response => {
response.data.results.map(movie => {
const details = getDetails(movie.id);
return movie.push(details);
});
});
};
const getDetails = id => {
axios
.get(
`https://api.themoviedb.org/3/movie/${id}?api_key=${API_KEY}&language=en-US`
)
.then(response => {
return response.data;
});
};
EDIT 1: I've played with the answer by Abanoub Istfanous, and it seemed to return undefined with the added dispatch to update my state. I tweaked it more and the second set of code marked WORKING seems to be displaying some search results, but I cannot render any of the appended data from getDetails to the screen, as they are showing undefined. Am I still doing something incorrectly?
NOT WORKING:
const search = async query => {
dispatch({
type: MOVIE_SEARCH_REQUEST
});
const response = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = Promise.all(
response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
})
);
dispatch({
type: MOVIE_SEARCH_COMPLETE,
payload: data
});
return data;
};
// getDetails is unchanged...
WORKING:
const search = async query => {
dispatch({
type: MOVIE_SEARCH_REQUEST
});
const response = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = Promise.all(
response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
})
);
dispatch({
type: MOVIE_SEARCH_COMPLETE,
payload: response.data.results
});
return data;
};
// getDetails is unchanged...

I recommend to use Promise
/*
*#return data
*/
const getDetails = async (id) => {
const response = await axios
.get(
`https://api.themoviedb.org/3/movie/${id}?api_key=${API_KEY}&language=en-US`
)
return response.data
};
then
const search = async (query) => {
const response = await axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = Promise.all(response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
}))
return data
};

The reason why it returns undefined is because Promise.all() will be executed asynchronously on the job queue and will only return the result after all Javascript gets executed and the call stack gets empty. So a simple solution to your code will be placing an await before Promise.all().
const search = async query => {
dispatch({
type: MOVIE_SEARCH_REQUEST
});
const response = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = await Promise.all(
response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
})
);
dispatch({
type: MOVIE_SEARCH_COMPLETE,
payload: response.data.results
});
return data; //No need to return data since you are dispatching it to redux store
};
You can read more about the asynchronous nature of JS on browsers here:
https://medium.com/#Rahulx1/understanding-event-loop-call-stack-event-job-queue-in-javascript-63dcd2c71ecd

Related

Awaiting dispatch actions to end

is it possible to await for dispatch action to end, so then, with that dispatched data before, I could use it in another function?
Currently I have this type of code, but I believe I shouldn't pass request to action and I should use
const url = '/someurl';
return (dispatch) => {
apiClient.get(url)
.then((res) => {
dispatch({
type: FETCH_SESSION_INFO,
payload: res,
});
});
};
instead of:
const url = '/someUrl';
const request = apiClient.get(url);
return {
type: FETCH_SESSION_INFO,
payload: request,
};
I want to do this:
fetchSessionInfo.then(() => doSomething(session.something))
where session.something is data we've just fetched from fetchSessionInfo, but when trying to use 1st version I get an information, that fetchSessionInfo is undefined

Data from an api depend on another api

Hi i wanna make an api call which depends another API. One API give me the ID from a random movie, and the second give me the details about that movie. All is separated in a file api.js and exported to a component. If i export the function i get a Promise how is fullfilled but not the movie details. How can i resolve this nested API? Here is the code if its help
export async function getMovieDetail() {
const data = await fetch(`https://api.themoviedb.org/3/trending/all/day?
api_key=${apiKey}`).then((res) => res.json());
const movie = data.results[random].id;
async function getMovieById() {
const data = await fetch(`https://api.themoviedb.org/3/movie/${movie}?
api_key=${apiKey}&language=en-US`).then((res) => res.json());
return data;
}
const movieDetail = await getMovieById();
return movieDetail;
}
chain both requests like this
export async function getMovieDetail() {
return fetch(`https://api.themoviedb.org/3/trending/all/day?
api_key=${apiKey}`)
.then((res) => res.json())
.then((data) => {
const movie = data.results[random].id;
return fetch(`https://api.themoviedb.org/3/movie/${movie}?
api_key=${apiKey}&language=en-US`)
}).then((res) => res.json());
}

Firebase returning two different outputs

I've made a simple class that returns the data downloaded from firebase. The issue is that if I console.log data in the class, it gives data as expected. However, if I import this class anywhere else and try to use it, it returns data as undefined.
Can you explain what's wrong?
My getCollection func in class dbAPI (data is correct)
getCollection(collection) {
dataBase
.collection(collection)
.get()
.then(querySnapshot => {
let data = querySnapshot.docs.map(doc => doc.data())
console.log(data)
return data
})
.catch(function(error) {})
}
The way I try to get data (data is undefined here)
componentDidMount() {
const db = new dbAPI()
let data = db.getCollection("collectionName")
this.setState({ data })}
The issue is that you're trying to return data from a callback to a synchronous function, which is impossible (link). You need to either promisify your getCollection function, or use async/await. If you want to convert your code to use async/await, check out this link. I put an example below. Basically, you need to await for the get request to get the data, then perform your transformation. After performing that transformation, you can return data.
async getCollection(collection) {
const collectionRef = dataBase.collection(collection);
try {
const dataSnapshot = await collectionRef.get();
const data = querySnapshot.docs.map(doc => doc.data());
console.log(data);
return data;
} catch (err) {
console.log(err);
}
}
In your componentDidMount, you must add async/await keywords to the appropriate functions. Async needs to go to the top to mark componentDidMount as async, and you need to await the data from the function call db.getCollection.
async componentDidMount() {
const db = new dbAPI()
const data = await db.getCollection("collectionName")
this.setState({ data })
}
Thanks to you I've managed to do it, really appreaciate your help, my solution:
getCollection:
async getCollection(collection) {
let data = null
await dataBase
.collection(collection)
.get()
.then(querySnapshot => {
data = querySnapshot.docs.map(doc => doc.data())
})
.catch(function(error) {
console.error(`Data fetch failed: \n${error}`)
data = "ERROR"
})
return data}
getting data:
async componentDidMount() {
const db = new Database()
const data = await db.getCollection("collectionName")
this.setState({ ...data })}

Creating Components based on multiple axios requests

I'm working on redux-thunk project which i need to reach out endpoint-1 to get which & how many components i need to show in a page. Then i'm required to use this return to reach out endpoint-2 and get the data with the parameters from the enpoint-1.
I'm having hard time to create the correct logic. Also i'm sharing a diagram which i hope gives you the idea and requirements.
Flow Diagram
Thanks
export const fetchByPage = () => async dispatch => {
const response = await streams.get("byPage?", {
params: parameters.byPageParams
});
console.log(response.data);
dispatch({ type: FETCH_BY_PAGE, payload: response.data });
};
export const fetchPersons = () => async dispatch => {
const response = await streams.get(URL, {
params: parameters.byCriteriaParams
});
dispatch({ type: FETCH_PERSONS, payload: response.data });
};
Here are my actions. I'm trying to update byCriteriaParams with the data returns from fetchByPage call.

Fetching data from store if exists or call API otherwise in React

Let's assume I have a component called BookOverview that displays details of a book.
I'm getting data with an action:
componentDidMount() {
this.props.getBook(areaId);
}
And then I get the data with axios:
export const getBook = () => async dispatch => {
const res = await axios.get(
`${API}/${ENDPOINT}`
);
dispatch({
type: GET_BOOK,
payload: res.data
});
};
How shall I change this code to:
if redux store already have the book loaded - return it
if no book is present in the store - call the relevant API?
What is the best practise to achieve that please?
You can have the getState inside your async action creator like this:
export const getBook = () => async (dispatch, getState) => {
if(!getState().book /* check if book not present */) {
const res = await axios.get(
`${API}/${ENDPOINT}`
);
dispatch({
type: GET_BOOK,
payload: res.data
});
} else {
dispatch({
type: GET_BOOK,
payload: getState().book
});
}
};
For More Async Actions-Redux
You can try it this way:
componentDidMount() {
if(this.props.book==null){
this.props.getBook(areaId);
}
}
I assumed that you have a property called book in your props. that populates from the particular reducer.
You have to subscribe the particular reducer to get the this.props.book - This gives the value that you have in your store.

Resources