I am using redux-thunk . Here, I have one login action. On that action I am calling an API which will give me some token, that I have to store in the state. Then immediately, after success of this action, I have to make another API request which will have this token in the header and will fetch more data. Based on this, I would like to redirect the user.
Login Action
import { generateToken } from '../APIs/login';
import HttpStatus from 'http-status-codes';
import { LOGIN_FAILED, LOGIN_SUCCESS } from '../constants/AppConstants';
import { fetchUserJd } from './GetUserJd';
import history from '../history';
export function fetchToken(bodyjson) {
return (dispatch) => {
getLoginDetails(dispatch, bodyjson);
}
}
export function getLoginDetails(dispatch, bodyjson) {
generateToken(bodyjson)
.then((response) => {
if (response.status === 200)
dispatch(sendToken(response.payload))
else
dispatch(redirectUser(response.status));
})
}
export function sendToken(data) {
return {
type: LOGIN_SUCCESS,
data: data,
}
}
export function redirectUser(data) {
return {
type: LOGIN_FAILED,
data: data,
}
}
2nd Action
import { FETCHING_JOBDESCRIPTION_SUCCESS, FETCHING_DATA_FAILED,FETCHING_JOBS } from '../constants/AppConstants';
import { getUserJobs } from '../APIs/GetUserJd';
import history from '../history';
export function fetchUserJd(token) {
console.log(token);
return (dispatch) => {
dispatch(fetchingJobDescription());
}
};
export function getUserJd(dispatch, token) {
getUserJobs(token)
.then((response) => {
if (response.status === 200)
dispatch(sendUserJd(response.payload))
else
dispatch(fetchFailure(response.status));
})
}
export function fetchFailure(data) {
return {
type: FETCHING_DATA_FAILED,
data: data,
}
}
export function sendUserJd(data) {
return {
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: data,
}
}
export function fetchingJobDescription() {
return {
type: FETCHING_JOBS
}
}
Calling this from
handleClick(event) {
event.preventDefault();
var bodyJson = {
"username": this.state.UserName,
"password": this.state.password
}
this.props.fetchToken(bodyJson);
}
How can I call that second action immediately after the success of the first request. ?
Tried way ->
componentWillReceiveProps(newProps) {
console.log(newProps.token);
if(newProps.token) {
this.props.fetchUserJd(newProps.token);
}
}
export function sendUserJd(data) {
if (data.data.length > 0) {
history.push('/userJobs');
} else {
history.push('/createJob');
}
return {
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: data,
}
}
You can do without setting it to redux state. You need to return your success action call to get the token in component itself using promise .then and then call this.props.sendToken(token); which will actually set the data in state and follows your existing flow.
handleClick(event) {
event.preventDefault();
var bodyJson = {
"username": this.state.UserName,
"password": this.state.password
}
this.props.getLoginDetails(bodyJson).then((token) => {
this.props.sendToken(token);
});
}
And in actions
const GET_LOGIN_DETAILS_SUCCESS = 'GET_LOGIN_DETAILS_SUCCESS';
export function getLoginDetailsSuccess(data) {
return {
type: GET_LOGIN_DETAILS_SUCCESS,
data: data,
}
}
export function getLoginDetails(bodyjson) {
generateToken(bodyjson)
.then((response) => {
if (response.status === 200)
return dispatch(getLoginDetailsSuccess(response.payload))
else
dispatch(redirectUser(response.status));
})
}
Let me know if you have any questions or if you feel difficult to understand
Related
I'm using vue3+typescript+pinia.
I am trying to follow the docs to crete tests but no success, got errors.
I want to test a store action which uses function that returns a promise.
EDITED:
The store pinia action
actions: {
async createContact(contact: Contact) {
console.log('this', this);
this.isLoading = true
ContactDataService.createContact(contact)
.then(response => {
this.sucess = true
console.log(response)
})
.catch(error => {
this.hasError = true
console.log(error);
})
this.isLoading = false
},
},
The exported class instance:
import Contact from "#/types/ContactType";
import http from "../http-commons";
class ContactDataService {
createContact(contact: Contact): Promise<any> {
const headers = {
"Content-Type": "application/json",
"accept": "*/*",
"Access-Control-Allow-Origin": "*"
}
return http.post("/contact", contact, { headers });
}
}
export default new ContactDataService();
The test:
import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach, vi } from "vitest";
import { useContactStore } from '#/stores/ContactStore'
import ContactDataService from "../../services/ContactDataService"
import Contact from '#/types/ContactType';
vi.mock('../../services/ContactDataService', () => {
const ContactDataService = vi.fn()
ContactDataService.prototype.createContact = vi.fn()
return { ContactDataService }
})
const contactExample: Contact = {
firstName: 'string',
lastName: 'string',
emailAddress: 'string',
}
describe('ContactStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('createContact', async () => {
const contactStore = useContactStore()
// expect(contactStore.sucess).toBeFalsy()
contactStore.createContact(contactExample)
// expect(contactStore.sucess).toBeTruthy()
})
})
When I run test I cant figure out how to mock the ContactDataService.createContact(contact) inside the action createContact.
Error: [vitest] No "default" export is defined on the "mock:/src/services/ContactDataService.ts"
For async requests, I'm using redux-saga.
In my component, I call an action to recover the user password, it its working but I need a way to know, in my component, that the action I dispatched was successfully executed, like this:
success below is returning:
payload: {email: "test#mail.com"}
type: "#user/RecoverUserPasswordRequest"
__proto__: Object
My component:
async function onSubmit(data) {
const success = await dispatch(recoverUserPasswordRequestAction(data.email))
if (success) {
// do something
}
}
My actions.js
export function recoverUserPasswordRequest(email) {
return {
type: actions.RECOVER_USER_PASSWORD_REQUEST,
payload: { email },
}
}
export function recoverUserPasswordSuccess(email) {
return {
type: actions.RECOVER_USER_PASSWORD_SUCCESS,
payload: { email },
}
}
export function recoverUserPasswordFailure() {
return {
type: actions.RECOVER_USER_PASSWORD_FAILURE,
}
}
My sagas.js
export function* recoverUserPassword({ payload }) {
const { email } = payload
try {
const response = yield call(api.patch, 'user/forgot-password', {
email
})
// response here if success is only a code 204
console.log('response', response)
yield put(recoverUserPasswordSuccess(email))
} catch (err) {
toast.error('User doesnt exists');
yield put(recoverUserPasswordFailure())
}
}
export default all([
takeLatest(RECOVER_USER_PASSWORD_REQUEST, recoverUserPassword),
])
In my reducer.js I dont have nothing related to recover the user's password, like a RECOVER_USER_PASSWORD_SUCCESS because like I said, the api response from my saga is only a code 204 with no informations
You should treat this as a state change in your application.
Add a reducer that receives these actions RECOVER_USER_PASSWORD_SUCCESS or RECOVER_USER_PASSWORD_FAILURE, then updates the store with information about request status. For example:
const initialState = {
email: null,
status: null,
}
const recoverPasswordReducer = (state=initialState, action) => {
//...
if (action.type === actions.RECOVER_USER_PASSWORD_SUCCESS) {
return {...initialState, status: True }
}
if (action.type === actions.RECOVER_USER_PASSWORD_SUCCESS) {
return {...initialState, status: False }
}
return state;
}
You can later have status as one of the fields selected in mapStateToProps when connect the component that needs to know about the status of the operation to the store.
function mapStateToProps(state) {
return {
/* ... other fields needed from state */
status: state.status
}
}
export connect(mapStateToProps)(ComponentNeedsToKnow)
I'm calling an action in componentDidMount as follows
componentDidMount() {
const { allowedEvcCards} = this.props;
allowedEvcCards(id);
}
With these actions i'm doing API calls and receiving some data as the response. I have set the data to a state with my reducer. I want to do some logic in the componentDidMount it self with the data received in the response.
For example in my reducer i'm doing this
case ALLOWED_EVC_SUCCESS:
return {
...state,
allowedEvc: action.data
}
And in componentDidMount i want to use allowedEvc . But it returns undefined as the action call is not complete at the time.
My action
// Get allowed Evc cards
export const ALLOWED_EVC_LOADING = 'ALLOWED_EVC_LOADING';
export const ALLOWED_EVC_SUCCESS = 'ALLOWED_EVC_SUCCESS';
export function allowedEvcCardsLoading() {
return {
type: ALLOWED_EVC_LOADING
}
}
export function allowedEvcCardsSuccess(data) {
return {
type: ALLOWED_EVC_SUCCESS,
data
}
}
export function allowedEvcCards(id) {
return dispatch => {
dispatch(allowedEvcCardsLoading());
axios.get(`${API_URL}/****/****/${id}/*****`, {
headers: {
// 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
console.log("Allowed EVC response ", res.data);
if (res.data.success === true) {
dispatch(allowedEvcCardsSuccess(res.data));
} else {
console.log("error");
// alert("error");
}
})
}
}
Unfortunately, componentDidMount is only called when a component is mounted. Unless, you unmount it you can't use that property. However, you could use componentDidUpdate since it is called as soon as it receives props.
Read more on this lifecycle method.
Edit: maybe you could try returning the axios promise along with the data and use it.
// Component
async componentDidMount() {
const { allowedEvcCards} = this.props;
const data = await allowedEvcCards(id);
// ... do something with data
}
// Action
export function allowedEvcCards(id) {
return dispatch => {
dispatch(allowedEvcCardsLoading());
return axios.get(`${API_URL}/****/****/${id}/*****`, {
headers: {
// 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
console.log("Allowed EVC response ", res.data);
if (res.data.success === true) {
dispatch(allowedEvcCardsSuccess(res.data));
return res.data;
} else {
console.log("error");
// alert("error");
}
})
}
}
I have an error in react native that says '.then is not a function' in this code where I use .this in .then(()=>{this.manageAccess()})
What can I do?
Or tell me if there is a replacement for .this
export function signIn(data) {
const request = axios({
method:"POST",
url:SIGNIN,
data:{
email: data.email,
password: data.password,
returnSecureToken:true
},
headers:{
"Content-Type":"application/json"
}
}).then( response => {
return response.data
}).catch(e =>{
return false
});
return {
type: SIGN_USER,
payload: request
}
}
class LoginForm extends Component {
manageAccess = () => {
if(!this.props.User.userData.uid){
this.setState({hasErrors:true})
} else {
setTokens(this.props.User.userData,()=>{
this.setState({hasErrors:false});
this.props.navigation.navigate('Dashboard')
})
}
};
submitUserHandler = ()=>{
let isFromValid = true;
let formToSubmit= {};
if(isFromValid){
if(this.state.type === "Login"){
this.props.signIn(formToSubmit).then(()=>{
this.manageAccess()
})
}
}
};
}
Your signIn() function returns an object where the request object is in the payload property
Try changing to
this.props.signIn(formToSubmit).payload.then(...
I have a component which has a form where at the moment to do clic on submit button, I call a function handleSubmit (it is on my component), this function call an action through of dispatch and this action, I make a call to a service (HTTP Request).
handleSubmit
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields((err, params) => {
if (!err) {
const { user, dispatch } = this.props;
let response = dispatch(actions.addDevice(params))
console.log(response); //Response is undefined
}
});
}
actions.addDevice
function addDevice(params){
return (dispatch, getState) => {
let { authentication } = getState();
dispatch(request({}));
service.addDevice(params, authentication.user.access_token)
.then(
response => {
if(response.status === 201) {
dispatch(success(response.data));
}
return response;
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
)
}
function request(response) { return { type: constants.ADD_DEVICE_REQUEST, response } }
function success(response) { return { type: constants.ADD_DEVICE_SUCCESS, response } }
function failure(error) { return { type: constants.ADD_DEVICE_FAILURE, error } }
}
service.addDevice
function addDevice(params, token){
return axios({
url: 'http://localhost:5000/user/add-device',
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token},
data: {
data1: params.data1,
data2: params.data2,
data3: params.data3
}
})
.then(function(response) {
return response;
})
.catch(function(error) {
return error.response;
});
}
I would like to get the response in my component to be able to make validations but as the request is async, I never can get the response and only prints an undefined variable. How can I get the response sync? Or what do I need do to be able to make validations?
You are not returning the promise service.addDevice.
So you can do return service.addDevice... and in the handleSubmit you do dispatch(...).then((data) => ...do something with the data...)
let response = dispatch(actions.addDevice(params))
this is asynchronous. So it is not surprising to return undefined from console.log(). console.log() execute even before dispatch process is completed. Use promise or async await syntax. I would recommend using the async-await syntax.
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields(async (err, params) => {
if (!err) {
const { user, dispatch } = this.props;
let response =await dispatch(actions.addDevice(params))
console.log(response); //Response is undefined
}
});
}
Please replace your code with this code
handleSubmit
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields((err, params) => {
if (!err) {
const { user, dispatch } = this.props;
dispatch(actions.addDevice(params)).then((response)=>{
console.log(response);
})
}
});
}
actions.addDevice
function addDevice(params){
return (dispatch, getState) => {
let { authentication } = getState();
dispatch(request({}));
return service.addDevice(params, authentication.user.access_token)
.then(
response => {
if(response.status === 201) {
dispatch(success(response.data));
}
return response;
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
)
}
function request(response) { return { type: constants.ADD_DEVICE_REQUEST, response } }
function success(response) { return { type: constants.ADD_DEVICE_SUCCESS, response } }
function failure(error) { return { type: constants.ADD_DEVICE_FAILURE, error } }
}