Creating Components based on multiple axios requests - reactjs

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.

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

Axios calls with React : best practises

i want to know if there is some clean code or update to make it on my code, because i think i repeat the same code on every actions on my redux, my question is how can I avoid calling axios on my actions files ?
Please take a look on my code here :
export const SignInType = (host, lang) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_SIGNINTYPE_REQUEST,
});
const { data } = await axios.get(
`/${lang}/data?host=${host}`
);
console.log({ data });
dispatch({
type: USER_LOGIN_SIGNINTYPE_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_CLEAR_ERROR,
});
} catch (err) {
dispatch({
type: USER_LOGIN_SIGNINTYPE_FAIL,
payload: err,
});
}
};
I Really want to delete the Axios name from my actions file and make it on a separate file, but how can i do this ?
Thank you
We can suggest but there's no correct answer to this, initially any redundant lines of code can be abstracted, so in order to make things a little bit easier, we need to abstract the obvious and add the meaningful, e.g:
abstract the way you write action creators:
const actionComposer = (options) => (...args) => async dispatch => {
const modifiedDispatch = (type, payload) => dispatch({ type, payload });
const { action, onSuccess, onFailed } = options(modifiedDispatch);
try {
if (action) {
const res = await action(...args)
onSuccess(res);
}
} catch (err) {
onFailed(err)
}
}
then your code can look like this:
export const SignInType = actionComposer((dispatch)=> {
return {
action: async (host, lang) => {
dispatch(USER_LOGIN_SIGNINTYPE_REQUEST);
const { data } = await axios.get(`/${lang}/data?host=${host}`);
return data;
},
onSuccess: (res) => {
dispatch(USER_LOGIN_SIGNINTYPE_SUCCESS, data);
dispatch(USER_LOGIN_CLEAR_ERROR);
},
onFailed: (err) => {
dispatch(USER_LOGIN_CLEAR_ERROR, err.message)
}
}
})
Redux Toolkit already has a createAsyncThunk API that does all the work of defining the action types and dispatching them for you. You should use that.
Alternately, you can use our RTK Query data fetching and caching library, which will eliminate the need to write any data fetching logic yourself.

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

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

Why are action function called wrapped inside dispatch in react?

I was just going through some react-redux code online and basically i came across the following set of code , which is basically a js file full of ACTIONS , just to give some context a combination of redux and redux-thunk is being used here:
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
// Action Creator to get the config object from the API
export const getConfig = () => async dispatch => {
const res = await tmdbAPI.get('/configuration');
dispatch({
type: TYPES.GET_CONFIG,
payload: res.data,
});
};
I am a bit confused as to why is getConfig function is being wrapped inside a dispatch ?
if you don't want, don't wrap that
like this
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
dispatch(await getConfig());
dispatch({ type: TYPES.REMOVE_LOADING });
};
//this is not using redux-thunk
// Action Creator to get the config object from the API
export const getConfig = async () => {
const res = await tmdbAPI.get('/configuration');
return {
type: TYPES.GET_CONFIG,
payload: res.data,
};
};
Important
but there are so many reasons why wrap the actions by dispatch.
Here is one example.
when you want get multiple data in one action step by step
export const getData = () => async dispatch => {
dispatch({
type: DATA_LOADING_START
});
try {
const res = await tmdbAPI.get('/url1');
dispatch({
type: DATA1_LOADED,
payload: res.data,
});
const res = await tmdbAPI.get('/url2');
dispatch({
type: DATA2_LOADED,
payload: res.data,
});
} catch (err) {
// handle error
}
dispatch({
type: DATA_LOADING_END
});
};
//this is using redux-thunk
#AlexanderSolonik
Question: Why wrap actions by dispatch?
because dispatch() sends the action result to the reducer.
Redux thunks are just redux actions that can perform side effects. So
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
Is just an async init function which when called performs the steps in a synchronous manner.
The key is that thunks can dispatch other thunks/actions/etc so the init thunk is just dispatching the getConfig() action which is itself async so the next step of the init function won't be performed until the config API call finishes (possibly because some other code depends on it)

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