First, apologize for my english.
I'm trying to make a request with redux-thunk.... I dont understan it well.
My idea is make a request using axios but method return undefined before return value of request.
I dont know if I'm passing dispatch well.
Can you help me please? What am I doing wrong???
This is how use dispatch in my component:
....
const mapDispatchToProps = dispatch => {
return {
createCustomersGeoJSON: () => dispatch(createCustomersGeoJSON()),
getAdvicesData: hierarchy => dispatch(getAdvicesData(hierarchy)),
getSocialNetworkData: () => dispatch(getSocialNetworkData()),
dispatch,
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(injectIntl(CustomersWidget));
In actions I do this:
export const getSocialNetworkData = () => {
return dispatch => {
dispatch({
type: GET_SOCIAL_NETWORK_DATA,
payload: fetchSocialNetworkData(),
});
};
};
And this is the code of fetchSocialNetworkData function:
axios
.get(`http://localhost:5000/socialRoute`)
.then(data => {
let response = Object.assign({}, data);
if (
response &&
response.data &&
response.data.tweets &&
Array.isArray(response.data.tweets)
) {
console.log("data.tweets: ", response.data.tweets);
return response.data.tweets;
}
return [];
})
.catch(error => {
console.log("Error gettin data from socialRoute: ", error);
});
It's because you think you're returning the response but what you're actually returning is nothing because you've handled the result of the promise in a .then chain.
You have two options:
Return a promise and resolve it in the .then:
function fetchSocialNetworkData() {
return new Promise((resolve) => {
axios
.get(`http://localhost:5000/socialRoute`)
.then(data => {
let response = Object.assign({}, data);
if (
response &&
response.data &&
response.data.tweets &&
Array.isArray(response.data.tweets)
) {
console.log("data.tweets: ", response.data.tweets);
resolve(response.data.tweets);
}
resolve([]);
})
})
}
OR
Use async/await (the modern way)
async function fetchSocialNetworkData() {
const data = await axios.get(`http://localhost:5000/socialRoute`);
let response = Object.assign({}, data);
if (
response &&
response.data &&
response.data.tweets &&
Array.isArray(response.data.tweets)
) {
console.log("data.tweets: ", response.data.tweets);
return response.data.tweets;
}
return [];
}
Both of these are the same thing under the hood. IE they're both different ways of writing a promise.
Now. in your thunk, you're still just calling that function, which means you're going to get the unresolved promise rather than the result of that promise. So the thunk becomes:
export const getSocialNetworkData = () => {
return async (dispatch) => {
dispatch({
type: GET_SOCIAL_NETWORK_DATA,
payload: await fetchSocialNetworkData(),
});
};
};
The thing to take away from this is that you can get far without understanding promises but that lack of understanding will always be a ceiling for your JS skills.
Related
I need function in redux-toolkit to fetch all data from others slices.
I have this code:
export const getAllData = createAsyncThunk(
'fetchRoot/getAllData',
async (_, { dispatch, rejectWithValue }) => {
const promises = [dispatch(getUsers()), dispatch(getSettings()), dispatch(getClients())];
Promise.all(promises)
.then((res: any) => {
// for (const promise of res) {
// console.log('SSS', promise);
// if (promise.meta.rejectedWithValue) {
// return rejectWithValue(promise.payload);
// }
}
})
.catch((err) => {
console.log(err);
});
}
);
My question: if one of slice fetch function (example: getUsers()) is rejected, how to reject promise.all?
getUsers() function and extraReducers:
export const getUsers = createAsyncThunk('users/getUsers', async (_, { rejectWithValue }) => {
try {
const res = await agent.Users.getAll();
return await res.data;
} catch (err) {
return rejectWithValue(err);
}
});
extraReducers: (builder) => {
builder
// GetUsers lifecycle ===================================
.addCase(getUsers.pending, (state) => {
state.apiState.loading = true;
state.apiState.error = null;
})
.addCase(getUsers.fulfilled, (state, { payload }) => {
state.apiState.loading = false;
state.data = payload;
})
.addCase(getUsers.rejected, (state, { payload }) => {
state.apiState.loading = false;
state.apiState.error = payload;
})
You have it basically right. Once the Promise.all(promises) has resolved you will have an array containing the resolved value of each of your individual thunks.
The individual promises will always resolve and will never reject. They will resolve to either a fulfilled action or a rejected action. In some cases, it will make sense to use the unwrap() property which causes rejected actions to throw errors. But looking at the .meta property will work too.
You can check your action with the isRejected or isRejectedWithValue functions which serve as type guards, that way you won't have any TypeScript errors when accessing properties like action.meta.rejectedWithValue.
The hard part here is trying to return rejectWithValue() from inside a loop. I would recommend unwrapping to throw an error instead.
import { createAsyncThunk, unwrapResult } from "#reduxjs/toolkit";
export const getAllData = createAsyncThunk(
"fetchRoot/getAllData",
async (_, { dispatch }) => {
const promises = [dispatch(getUsers()), dispatch(getSettings()), dispatch(getClients())];
const actions = await Promise.all(promises);
return actions.map(unwrapResult);
}
);
Note that there is no reason to try/catch in your getUsers thunk if you are going to rejectWithValue with the entire caught error object. Just let the error be thrown.
export const getUsers = createAsyncThunk('users/getUsers', async () => {
const res = await agent.Users.getAll();
return res.data;
});
I am getting this error with TS and Redux and cant figure it out why it happens and how its could be fixed.
I am using redux thunk middleware.
Redux TS Error Actions must be plain objects. Instead, the actual type was: 'Promise'.
Seems that issue starts to happen in updateUserSession(data)
which gets started this way:
insertUserSession(data)
.then(
(res: IUserSession) => {
updateUserSession(data)
},
err => {
console.error("Insert session failed", err)
},
)
.then(() => {
//Here will continue another async action
})
export const insertUserSession = (session: IUserSession): Promise<IUserSession> => {
return axios.post("/user/" + session?.userId + "/session", session)
}
export const updateUserSession = (data: IUserSession) => {
return axios.put('/user/' + data.userId + '/session', data)
.then(
(response) => {
fetchUserSession(store.getState().user?.userData?.username)
.then(
(res) => {
if (typeof res.data === "object" && !Object.keys(res.data).length)
saveUserSession(res.data)
},
(err) => {
console.error(err);
},
)
},
(err) => {
console.error(err);
},
)
}
export const saveUserSession = (data: IUserSession) => {
return (dispatch: Dispatch<UserAction>) => {
dispatch({
type: UserType.SAVE_USER_SESSION,
payload: data,
});
}
}
This is happening because updateUserSession is actually a thunk and not an action. It seems saveUserSession is the action you need to call with callback data.
Be sure to declare the correct thunk signature:
const updateUserSession = (data: IUserSession) => (dispatch, getState) => {}
Also, don't forget to call dispatch to fire actions inside the thunk.
I am using a promise based hook in a React app to fetch async data from an API.
I am also using a Axios, a promise based http client to call the API.
Is it an anti-pattern to use a promise based client inside another promise? The below code does not seem to work.
const getData = () => {
return new Promise((resolve, reject) => {
const url = "/getData";
axios.get(url)
.then(function(response) {
resolve(response);
})
.catch(function(error) {
reject(error);
});
});
const useAsync = (asyncFunction) => {
const [value, setValue] = useState(null);
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then(response => setValue(response))
.catch(error => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { execute, pending, value, error };
};
};
const RidesList = () => {
const {
pending,
value,
error,
} = useAsync(getData);
Oh man. I think you have a fundamental misunderstanding about how Promises work.
First, axios already returns a Promise by default. So your whole first function of getData can be reduced to:
const getData = () => {
const url = "/getData"
return axios.get(url)
}
But the meat of your code seems to indicate you want a querable Promise - so you can check the status of it for whatever reason. Here's an example of how you would do it, adapted from this snippet:
function statusPromiseMaker(promise) {
if (promise.isResolved) return promise
let status = {
pending: true,
rejected: false,
fulfilled: false
}
let result = promise.then(
resolvedValue => {
status.fulfilled = true
return resolvedValue
},
rejectedError => {
status.rejected = true
throw rejectedError
}
)
.finally(() => {
status.pending = false
})
result.status = () => status
return result
}
In this way, you can then do something like let thing = statusPromiseMaker(getData()) and if you look up thing.status.pending you'll get true or false etc...
I didn't actually run what's above, I may have forgotten a bracket or two, but hopefully this helps.
I have to admit - I haven't seen anything like this ever used in the wild. I am interested in knowing what you're actually trying to accomplish by this.
Axios itself returns a promise but if you want to make a custom class having your custom logic after each API call then you can use interceptors I was having the same requirement and this is how I am returning promises after applying my custom logic on each API call.
Interceptors will get executed separately after and before each request you made so we can simply use them if we want to modify our request or response.
here is my working solution have a look at it.
callApi = (method, endpoint, params) => {
this.apiHandler.interceptors.request.use((config) => {
config.method = method
config.url = config.baseURL + endpoint
config.params = params
return config
})
return new Promise((resolve, reject) => {
this.apiHandler.interceptors.response.use((config) => {
if (config.status == 200) {
resolve(config.data)
} else {
reject(config.status)
}
// return config
}, error => reject(error))
this.apiHandler()
})
}
Below is the code to call this function
helper.callApi("get", "wo/getAllWorkOrders").then(d => {
console.log(d)
})
I use redux for dispatch an action creator (AC).
In this AC, I fetch some data to an external API (Melissa) with redux-thunk.
This AC works fine but if I try to capture this data that is set by the reducer in the store just after the call of the AC, this value is not already set.
How I can wait that my AC is finish to exucute the next line of my code ?
My AC is :
export function fetchMellisa (value) {
return dispatch => {
axios.get( /*URL*/ )
.then( ({ data }) => {
dispatch( { type: ACTION_CREATOR_1 , payload: data }
)
} ).catch( error => console.error( 'error', error ) );
};
}
and my call:
this.props.dispatch(fetchMellisa(values.currentAddress));
const currentAddress = this.props.addressMelissa[ 0 ].Address ;
this.props.addressMelissa[0].Address is my value set on the store by the reducer.
Try changing your thunk to return a Promise. This is a good rule to enforce on your thunks.
export function fetchMellisa(url) {
return dispatch =>
axios
.get(url)
.then(({ data }) => dispatch({ type: ACTION_CREATOR_1, payload: data }))
.catch(error => {
console.error("error", error);
return Promise.reject();
});
}
Then you can use it like this:
const url = values.currentAddress;
this.props.dispatch(fetchMellisa(url)).then(() => {
const newAddress = this.props.addressMelissa[0].Address;
});
the component should re-render with the new data in props, the api call should be in a lifecycle method such as componentDidMount or in an event handler. if you needed to use it inline, then you should return the axios.get and chain a .then on your original dispatch.
export function fetchMellisa (value) {
return dispatch => {
return axios.get( /*URL*/ )
.then( ({ data }) => {
dispatch( { type: ACTION_CREATOR_1 , payload: data }
)
} ).catch( error => console.error( 'error', error ) );
};
}
this.startLoadingMellisa();
this.props.dispatch(fetchMellisa(values.currentAddress))
.then( response => {
this.stopLoadingMellisa();
// do something that releases the loading state
})
if ( this.state.loading ) { return <SomeLoadingComponentOrMsg/> }
const currentAddress = this.props.addressMelissa[ 0 ].Address ;
i didn't create everything to save time but i think there is enough here to give a good picture.
I have the following handler that dispatches two actions...
_onPress = () => {
let { request,userId} = this.props;
this.props.dispatch(acceptRequest(request.id,userId))
this.props.dispatch(navigatePop());
}
What I would like this to look like instead is the following...
_onPress = () => {
let { request,userId} = this.props;
this.props.dispatch(acceptRequest(request.id,userId))
.then(this.props.dispatch(navigatePop()))
}
My ActionCreator looks like this...
export function acceptRequest(requestId,fulfilledBy){
return dispatch => {
fulfillments.create(requestId,fulfilledBy)
.then(response => response.json())
.then(fulfillment => {
dispatch(_acceptRequestSuccess(fulfillment))
})
.catch(err => {
console.log(err);
dispatch(_acceptRequestError(error))
})
}
}
I am aware that they have many middleware(s) that people suggest, but I don't
see how any of them fit this scenario unless I am completed doing somthing
incorrectly.
In my particular case, I only want to dispatch the seond action if the first is
successful, but I don't want to do this from the action creator because then
it is less reusable.
Ok, so I misunderstood you, you should mention that you are using redux-thunk.
I don't think redux action should return a promise, I think you should do everything in the component and then play with the promise, like this:
_onPress = () => {
let { request,userId} = this.props;
fulfillments.create(requestId,fulfilledBy)
.then(response => response.json())
.then(fulfillment => {
this.props.dispatch(_acceptRequestSuccess(fulfillment))
})
.then(() => {
this.props.dispatch(navigatePop());
})
.catch(err => {
console.log(err);
this.props.dispatch(_acceptRequestError(error))
})
}
But if you still want to return a promise you can make the function inside the action return the promise:
export function acceptRequest(requestId,fulfilledBy){
return dispatch => {
return fulfillments.create(requestId,fulfilledBy)
.then(response => response.json())
.then(fulfillment => {
dispatch(_acceptRequestSuccess(fulfillment))
})
.catch(err => {
console.log(err);
dispatch(_acceptRequestError(error))
})
}
}