Redux-thunk: API response keeps pending - javascript

I am trying to develop an application, that is showing photos from Unsplash given a keyword. I managed to fetch specific photos using unsplash.js. In my actions, I have several action creators:
export const fetchPhotos = () => ({
type: FETCH_PHOTOS
});
export const receivePhotos = term => {
const unsplash = new Unsplash({
applicationId:
"id",
secret: "secret",
callbackUrl: "callback"
});
console.log(term);
const response = unsplash.search
.photos(term, 1, 20)
.then(toJson)
.then(json => json)
.then(json => json)
console.log(response.then(results => results));
return {
type: RECEIVE_PHOTOS,
payload: response
};
}
export const unsplash = (term) => dispatch => {
console.log(term);
dispatch(fetchPhotos());
setTimeout(() => {
dispatch(receivePhotos(term));
console.log("dispatching")
return Promise.resolve();
}, 1000)
}
My reducers then do:
const initialState = {
isFetching: false,
sortDirection: null,
sortKey: null,
items: []
}
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_PHOTOS:
console.log(state, "Fetch photos reducer");
return {
...state,
isFetching: true
};
case RECEIVE_PHOTOS:
console.log("Receive photos reducer", action.payload)
return {
...state,
isFetching: false,
items: action.payload
};
case SET_SORT:
return {
...state,
sortKey: action.sortKey,
sortDirection: action.sortDirection
};
default:
return state;
}
}
However, as the receivePhotos action creator calls an API, I have a promise that needs to be resolved in order for the whole application to work. My fetch photos reducer is console logging the action, then the Promise appears, however it is always on pending. Then my receivePhotos action creator dispatches to the reducer and I can see that this is a Promise:
How can I resolve this promise?

In the below code you assign a promise to response, then console.log that promise, and then return the action with payload set to that promise.
const response = unsplash.search
.photos(term, 1, 20)
.then(toJson)
.then(json => json)
.then(json => json)
console.log(response.then(results => results));
return {
type: RECEIVE_PHOTOS,
payload: response
};
dispatch(receivePhotos(term)); then dispatches that action, still with the payload as a promise. maybe this would work if you had middleware that could handle it.
This use of dispatch suggests that you are using redux-thunk though.
In that case, you should do the same with receivePhotos, include the fetchPhotos call, and retire the unsplash action.
const unsplashClient = new Unsplash({
applicationId:
"id",
secret: "secret",
callbackUrl: "callback"
});
export const receivePhotos = term => dispatch => {
dispatch(fetchPhotos());
return unsplashClient.search
.photos(term, 1, 20)
.then(toJson)
.then(json => dispatch({
type: RECEIVE_PHOTOS,
payload: json
});
}
In the end I would suggest a bit of refactoring of the actions and the (related reducers) such as:
const unsplashClient = new Unsplash({
applicationId:
"id",
secret: "secret",
callbackUrl: "callback"
});
export const fetchingPhotos = payload => ({
type: FETCHING_PHOTOS, payload
});
export const setPhotos = payload => ({
type: SET_PHOTOS, payload
});
export const fetchPhotos = term => dispatch => {
dispatch(fetchingPhotos(true));
return unsplashClient.search
.photos(term, 1, 20)
.then(toJson)
.then(json => {
dispatch(setPhotos(json));
dispatch(fetchingPhotos(false));
});
}

Related

Redux Toolkit: createAsyncThunk action dispatched and return rejected promise the action returns empty payload

There is action created using createAsyncThunk and this method return a promise which is handled like this
export const action = createAsyncThunk(
'/fullfilment/update-content-preference',
async (data, { getState }, thunkAPI) => {
try {
const state = getState();
const authToken = state.auth.token;
const { selectedShipmentId: shipmentId } = state.fullfilment;
const obj = {
...data,
shipmentId
};
const response = await axios.post(
'/fullfilment/update-content-preference',
obj,
{ headers: { Authorization: `Bearer ${authToken}` } }
);
return response;
} catch (err) {
if (err.response) {
return thunkAPI.rejectWithValue({
err: err.response,
status: err.response.status
});
}
return thunkAPI.rejectWithValue({
err: 'Network Error'
});
}
}
);
and below are the promises are handled
[action.pending]: state => ({
...state,
loading: true
}),
[action.fulfilled]: (state, action) => ({
...state,
data: {
...state.data,
loading: false,
list: action.payload.data.list
message: action.payload.data.message
}
}),
[action.rejected]: (state, action) => ({
...state,
loading: false
error: action.payload.data.error
message: action.payload.data.message
})
pending resolved and rejected all working well but in rejected i am unable to fetch the data but where action is dispatching thunkApi with error i am getting data there why not in rejected promise. any help would be appreciated thanks
You pass err to rejected but you forgot use it.
error: action.payload.err.data.error
message: action.payload.err.data.message
And you are declare thunkAPI wrong way. Just update like this:
async (data, thunkAPI) => {
const { getState } = thunkAPI;
}

Promise.all() keeps running even after error is thrown

in my React component, I have this:
componentDidMount() {
const {
dispatchGetCompanies,
dispatchGetDocumentTopics,
dispatchGetDataForCompanyTopicList,
dispatchGetDocumentTypes,
dispatchRequestComplete,
dispatchRequestInProgress,
dispatchRequestError,
} = this.props;
dispatchRequestInProgress()
Promise.all([
dispatchGetCompanies(),
dispatchGetDocumentTopics(),
dispatchGetDocumentTypes(),
dispatchGetDataForCompanyTopicList(),
]).then(() => {
dispatchRequestComplete();
}).catch((error) => {
dispatchRequestError(error);
});
}
const mapDispatchToProps = function (dispatch) {
return {
dispatchGetCompanies: (...args) => dispatch(getCompanies(...args)),
dispatchGetDocumentTopics: (...args) => dispatch(getDocumentTopics(...args)),
dispatchGetDataForCompanyTopicList: (...args) => dispatch(getDataForCompanyTopicList(...args)),
dispatchDeleteCompanyTopic: (...args) => dispatch(deleteCompanyTopic(...args)),
dispatchGetDocumentTypes: (...args) => dispatch(getDocumentTypes(...args)),
dispatchRequestInProgress: (...args) => dispatch(requestInProgress(...args)),
dispatchRequestComplete: (...args) => dispatch(requestComplete(...args)),
dispatchRequestError: (...args) => dispatch(requestError(...args)),
};
};
The action for getDocumentTypes is this:
export function getDocumentTypes() {
return function (dispatch, getState, api) {
return api.get('/document-type-metadata')
.then(({data}) => dispatch(getDocumentTypesSuccess(data)));
};
}
export function getDocumentTypesSuccess(data) {
return {
type: GET_DOCUMENT_TYPES,
payload: data,
};
}
Reducer code is this:
function setDocumentTypes(state, action) {
const {
documentTypeMetadata
} = action.payload;
return state.merge({
documentTypes: List(documentTypeMetadata),
_metadata: Map({
isFetching: false,
}),
});
}
export default function (state = Map({
id: null,
companyTopics: List(),
companies: List(),
topics: List(),
documentTypes: List(),
_metadata: Map({
isFetching: false,
error: false,
}),
}), action) {
switch (action.type) {
case SECTION_IDENTIFICATION_REQUEST_COMPLETE:
return setRequestComplete(state, action);
case SECTION_IDENTIFICATION_REQUEST_IN_PROGRESS:
return setInProgress(state, action);
case SECTION_IDENTIFICATION_REQUEST_ERROR:
return setError(state, action);
case GET_COMPANIES:
return setCompanies(state, action);
case GET_DOCUMENT_TOPICS:
return setTopics(state, action);
case GET_COMPANY_TOPIC_LIST_DATA:
return setCompanyTopicListData(state, action);
case GET_DOCUMENT_TYPES:
return setDocumentTypes(state, action);
default:
return state;
}
}
The dispatchRequestError is similar; it sets the "error" object in the Redux store to whatever error is returned.
The problem I am having is that if the api call to getDocumentTypes returns back an error, I would expect the dispatchRequestError to immediately execute, with all the other promises (dispatchGetDocumentTopics, etc) being rejected. However, after the error is set, the other promises inside the Promise.all() still resolve, causing the error property to become undefined, which causes the error message that I'm showing on the page to appear, then quickly disappear.
What is going wrong?
Thank you

Using a callback while dispatching actions

I am trying to make a GET request for some data.
Here is my action call.
componentDidMount() {
this.props.fetchData(() => {
this.setState({ isLoading: false });
});
}
Prior to completion I'd like to display "Loading..." momentarily as the fetch request is making it's trip. I'm using a callback for this and setting my local state.
Here is my action creator with a 'callback'.
export function fetchData(callback) {
return (dispatch) => {
axios.get(`/api/fetchsomething`)
.then(() => callback())
.catch((err) => {
console.log(err.message);
});
}
}
And here is that same function above but dispatching the action so that I can receive as props and render to my ui.
export function fetchData(callback) {
return (dispatch) => {
axios.get(`/api/fetchsomething`)
.then((response) => dispatch({ type: FETCH_DATA, payload: response }))
.catch((err) => {
console.log(err.message);
});
}
}
My question is how do you make the callback and dispatch the action in the same action creator function? Is that even good practice?
You could do something like this
componentDidMount() {
this.setState({ isLoading: true }, () => {
// ensuring that you make the API request only
// after the local state `isLoading` is set to `true`
this.props.fetchData().then(() => this.setState({ isLoading: false });
});
}
and, fetchData would be defined as follows
export function fetchData(callback) {
return (dispatch) => {
return axios.get(`/api/fetchsomething`)
.then((response) => dispatch({ type: FETCH_DATA, payload: response }))
.catch((err) => console.log(err.message));
}
}
If you're using the redux-thunk middleware to use asynchronous actions, then these actions will return Promises; so you can set your component's local state after that Promise resolves.
In the component:
.....
componentDidMount() {
this.props.fetchData();
}
....
export default connect((state) => ({loading: state.loading, data: state.data}))(Component);
In the actions, you should do :
....
export function fetchData() {
return (dispatch) => {
dispatch({ type: FETCHING_DATA}); //dispatch an action for loading state to set it to true
return axios.get(`/api/fetchsomething`)
.then((response) => dispatch({ type: DATA_FETCHED, payload: response }))
.catch((err) => console.log(err.message));
}
}
....
In the reducer, you should do :
....
case 'FETCHING_DATA':
return {
...state,
loading: true,
}
case 'DATA_FETCHED':
return {
...state,
data: action.payload,
loading: false,
}
....
I personally feel that you shouldn't put any business logic in your component because it can cause some problems later when you want to refactor your app. This means that there shouldn't be any .then in your component and everything should be guided through redux (if there is some side effects in your app). So, you should control your loading state from redux itself and not inside the component.

Undefined in store reducer

Im trying to write reducer for action in angular ngrx store architecture:
this is my reducer:
export const registration = (state: any, {type, payload}) => {
switch (type) {
case 'REGISTER_USER':
console.log('reducer ' + payload, type);
return payload;
default:
return state;
}
};
and this is my function calling the reducer
register(user: RegistrationUser) {
return this.http.post(Global.API_URL+'/register', user)
.map(response => response.json())
.map(data => ({type: 'REGISTER_USER', data}))
.subscribe(action => {this.store.dispatch(action); console.log(action)});
}
the problem Im having in that the payload is undefined. Where the
console.log(action);
returns object. And console log from reducer returns proper action type but undefined as object 'reducer undefined REGISTER_USER'
I think you need to just map data to payload: data:
register(user: RegistrationUser) {
return this.http.post(Global.API_URL+'/register', user)
.map(response => response.json())
.map(data => ({type: 'REGISTER_USER', payload: data}))
.subscribe(action => {this.store.dispatch(action); console.log(action)});
}

Parse XML in react-redux-promise app

The data source for my app only provides data in XML format.
I use axios to get the XML data. It ends up as a string in the data section of the result.
I have tried to use xml2js to convert it, but it just fires off a async job and returns, so I dont get the redux-promise middelware to work. The payload is nothing when the reducers sends the data to the component that should render it.
Not sure if this makes sense, but can I make the reducer wait for the new function call to return before sending the data on the the component?
action index.js
export function fetchData(jobid, dest) {
const url = `${DATA_URL}jobid=${jobid}&refdist=${dest}`;
const request = axios.get(url);
console.log(request);
return {
type: FETCH_DATA,
payload: request
}
}
my reducer
export default function (state = [], action) {
console.log(action);
switch (action.type) {
case FETCH_DATA:
console.log("pre");
parseString(action.payload.data, function (err, result) {
// Do I need some magic here??? or somewhere else?
console.dir(result);
});
return [action.payload.data, ...state];
}
return state;
}
you should change your action creator code, because axios is async. And dispatch action after receive data.
You don't need this logic in reducer.
For async actions you may use redux-thunk
export const fetchData = (jobid, dest)=>dispatch =>{
const url = `${DATA_URL}jobid=${jobid}&refdist=${dest}`;
const request = axios.get(url).then(res=>{
parseString(res, function (err, result) {
if(result){
dispatch({
type: FETCH_DATA,
data:result
})
}
if(err) throw err
});
}).catch(err=>console.error(error))
};
///clean reducer
export default function (state = [], action) {
switch (action.type) {
case FETCH_DATA:
return [...state, action.data ];
}
return state;
}
Also you may need to know about fetching process: loading, success , failure.Then action creator may looks like:
export const fetchData = (jobid, dest)=>dispatch =>{
const url = `${DATA_URL}jobid=${jobid}&refdist=${dest}`;
dispatch({
type: FETCH_DATA_REQUEST,
data:result,
isFetching:true
})
const request = axios.get(url).then(res=>{
parseString(res, function (err, result) {
if(result){
dispatch({
type: FETCH_DATA_SUCCESS,
data:result,
isFetching:false
})
}
if(err) throw err
});
}).catch(err=>{
dispatch({
type: FETCH_DATA_FAILURE,
err:err,
isFetching:false
})
console.error(error)
})
};

Categories