Then() is not working after the dispatched action in React - javascript

I have the following Login function:
export function Login(username, password) {
return dispatch => {
dispatch(loginBegin());
axios({
method: 'post',
url: 'api/User/Login',
data: { username, password },
}).then(response => {
if (response.data !== null) {
console.log('success: Login is successful');
dispatch(loginSuccess(response.data));
}
}).catch(error => { dispatch(loginFailure(error.response.data.message)) });
}
}
I call this function as follows in the Login component:
function FormSubmitHandle(values) {
setFormSubmitted(true);
props.login(values.username, values.password)
.then((login_succeeded) => {
console.log('YESSSS');
console.log(login_succeeded);
});
}
However, .then() part is not working. It does not print out anything.
Any ideas why this is happening?

You should return:
return axios({
method: 'post',
url: 'api/User/Login',
data: { username, password },
}).then(response => { ....
Take a look here: is it considered good practice to pass callBacks to redux async action?
Although, I wonder what kind of thing you wish to do with that. When dealing with flux based patterns (such as Redux), we should keep the data flow in one direction (keep that in mind)

you can change it like below:
export function Login(username, password) {
return async (dispatch) => {
dispatch(loginBegin());
try{
let res = await axios({
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json'
},
'method': 'post',
'url': 'api/User/Login',
data: { username, password },
});
console.log('success: Login is successful');
dispatch(loginSuccess(res));
}
catch(error) { dispatch(loginFailure(error.response.data.message)) });
}
then you can use it like below:
cosnt FormSubmitHandle = async (values)=> {
setFormSubmitted(true);
let res = awiat props.login(values.username, values.password);
console.log('YESSSS');
}

Related

Redux: Call the same api but dispatch different actions

Currently I have a redux actions which is getUsers() to get all users.
export function getUsers (params) {
return async dispatch => {
await dispatch({ type: 'GET_USERS_REQUEST' });
const urlParams = params ? new URLSearchParams(Object.entries(params)) : null;
return axios({
method: 'get',
url: Environment.GET_USERS+ `?${urlParams}`,
headers: { 'Content-Type': 'application/json' },
}).then (async res => {
await dispatch({ type: 'GET_USERS_SUCCESS', allUsers: res.data.Data });
}).catch (err => {
dispatch({ type: 'GET_USERS_FAILURE', error: err.toString() });
});
}
}
Now I want to use the same getUsers() but with param id (eg. getUsers({ UserId: 'jamesbond007' })),
and in the action I want to dispatch({ type: 'GET_USER_BY_ID_SUCCESS', user: res.data.Data })
How can I dispatch different actions with the same api call? Should I duplicate the same code but change the action dispatch? If doing so it becomes repetitive function.
You can decide the action by param id, like
// all users, paramId is null
// a user, paramId is xxxx
const action = paramId ?
{ type: 'GET_USER_BY_ID_SUCCESS', user: res.data.Data } :
{ type: 'GET_USERS_SUCCESS', allUsers: res.data.Data };
dispatch(action);
But, I think your idea is not good. It is better to do the logic in 2 methods. It is readable.
I found way to do this. I declare variables REQUEST, SUCCESS, FAILURE and check if the param object contains UserId. If yes, it will dispatch GET_USER_BY_ID instead of GET_USERS.
export function getUsers (params) {
let REQUEST, SUCCESS, FAILURE;
if (!_.isEmpty(params) && params.UserId) {
REQUEST = 'GET_USER_BY_ID_REQUEST';
SUCCESS = 'GET_USER_BY_ID_SUCCESS';
FAILURE = 'GET_USER_BY_ID_FAILURE';
} else {
REQUEST = 'GET_USERS_REQUEST';
SUCCESS = 'GET_USERS_SUCCESS';
FAILURE = 'GET_USERS_FAILURE';
}
return async dispatch => {
await dispatch({ type: REQUEST });
const urlParams = params ? new URLSearchParams(Object.entries(params)) : null;
return axios({
method: 'get',
url: Environment.GET_USERS+ `?${urlParams}`,
headers: { 'Content-Type': 'application/json' },
}).then (async res => {
await dispatch({ type: SUCCESS, payload: res.data.Data });
}).catch (err => {
dispatch({ type: FAILURE, error: err.toString() });
});
}
}
Note that in my reducer, I use only action.payload. Eg:
case 'GET_USERS_SUCCESS':
return { ...state, isFetching: false, userList: action.payload };
case 'GET_USER_BY_ID_SUCCESS':
return { ...state, isFetching: false, user: action.payload };

Nested axios calls updating react state before all requests have finished

I am doing some nested axios calls as in order to create the object i want i need to fire off a couple of different API's. My problem is that when i call setState, the state will start updating before all requests have finished so my table will populate entry by entry which does not look nice.
here is my code:
fetchServices = async ()=> {
this.setState({isLoading: true})
await axios({
method: 'get',
url :getApiUrl(),
headers:{
"Accept": "application/json"
}
})
.then( async response => {
let Data: any[] = []
response.data.Message.forEach(async (e: any) => {
await axios({
method: 'get',
url: getApiUrl() + "/" + e.organisationErn + "/services",
headers:{
"Accept": "application/json"
}
}).then( async response => {
console.log(response)
if(response.data.Message !== "No services found"){
response.data.Message.forEach(async(e:any)=>{
let orgName = await axios({
method: 'get',
url: getApiUrl() + "/" + e.organisationErn,
headers:{"Accept": "application/json"}})
.then(response => {return response.data.Message.organisationName});
let entry = {
servicename: { text: e.serviceName },
servicetype: { text: e.serviceTypeDescription },
organisation: { text: orgName },
};
Data.push(entry);
this.setState({ tableData: Data });
})
};
});
});
setTimeout(()=>{this.setState({isLoading: false})}, 100)
}).catch(error => {
alert(error)
});
};
Well you are pushing your data one by one. You can use Promise.all to await all requests complete then populate your state.
if (response.data.Message !== "No services found") {
const promises = response.data.Message.map((e: any) => {
axios({
method: 'get',
url: getApiUrl() + "/" + e.organisationErn,
headers: {
"Accept": "application/json"
}
})
.then(response => {
return response.data.Message.organisationName
})
.then(org => {
return {
servicename: {
text: e.serviceName
},
servicetype: {
text: e.serviceTypeDescription
},
organisation: {
text: org
},
};
});
});
const Data = await Promise.all([...promises]);
this.setState({ tableData: Data });
BTW there may be some parentheses errors.
I think its better to get the result from each axios request and use it at the end, instead of using then
like
const result1= await axios.get(...);
const result1= await axios.get(...);
const result1= await axios.get(...);
setState({ tableData: Data });
you can also refer https://medium.com/better-programming/how-to-use-async-await-with-axios-in-react-e07daac2905f

How use await keyword along with asyncstorage setitem for server response?

I'm trying to use asyncstorage in my react native app.The problem is the server response I'm getting takes some delay so I want to wait for the response then I want to use that responseData.user_id to be saved in my app.I'm using nodejs as backend and mysql db.So after user registration I'm inserting it to db at the same time I've written another query for fetching their user_id (PK).So this responseData is getting to client and I'm trying to take that user_id from the response.So I've written something like this
onPressRegister = async () => {
try {
let response = await fetch('http://192.168.1.2:3000/users/registration', {
method: 'POST',
headers: {
'Accept': 'applictaion/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
contact: this.state.contact,
password: this.state.password,
})
});
let responseData = await response.json();
if (responseData) {
try {
Action.firstScreen();
await AsyncStorage.setItem('userid', JSON.stringify(responseData.userData.phone_no));
}
catch (e) {
console.log('caught error', e);
}
}
} catch (error) {
console.error(error)
}
}
And in my next screen I'm accessing the userid like this.And passing it the next API call like this.
getUserId = async () => {
let userId = await AsyncStorage.getItem('userid');
return userId;
}
onPressYes = (workType) => {
this.getUserId().then((userId) => {
this.setState({userId:userId})
})
fetch('http://192.168.1.2:3000/users/user_request',{
method:'POST',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
workType,
phone:this.state.userId
})
})
.then(response => response.json())
.then((responseData) => {
this.setState({
data:responseData
});
});
}
But this is the error I'm getting.
Try this:
onPressRegister = async () => {
try {
let response = await fetch('http://192.168.1.6:3000/users/registration', {
method: 'POST',
headers: {
'Accept': 'applictaion/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
contact: this.state.contact,
password: this.state.password,
})
});
let responseData = await response.json();
if (responseData) {
try {
await AsyncStorage.setItem('userid', JSON.stringify(responseData.user_id));
}
catch (e) {
console.log('caught error', e);
}
}
} catch (error) {
console.error(error)
}
}
To access the value in some other component:
getUserId = async () => {
let userId = await AsyncStorage.getItem('userid');
return userId;
}
componentWillMount() {
this.getUserId().then((userId) => {
console.log(userId);
})
}

React testing onSubmit using axios

I recently started testing my React app. However, I stumbled when dealing with submitting forms. My test covers most of the lines but misses out on actual part of submit form method.
LoginForm.js - submit form
const userLoginData = {
userId : this.state.userId,
password : this.state.password,
userType : this.state.userType
};
axios({
data : JSON.stringify(userLoginData),
type : 'post',
url : Constant.BASE_URL_SERVER+'/rest/login',
headers : {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache : false
})
.then(function (response) {
//alert("Form Submitted.");
this.setState({isLoggedIn : true});
this.setState({loginResponse : "Login Success!"});
if(this.state.userType === 'Customer'){
...
login_form-test.js
describe('testing form submission onSubmit', () => {
const testData = {
userId: '00000000',
password: 'SamplePassword0',
userType: 'Customer',
validForm: true,
}
it('should submit form onSubmit()', () => {
const mountedComponentHandle = mount(<LoginForm {...testData}/>);
const onSubmitForm = sinon.spy(
mountedComponentHandle.instance(),
'handleSubmitForm'
);
mountedComponentHandle.update();
const formHandle = mountedComponentHandle.find('form');
expect(formHandle.length).toBe(1);
formHandle.simulate('submit');
expect(onSubmitForm.called).toBe(true);
});
});
Please suggest on how to test .then() and .catch() of axios.
Thanks.
Key here is to make your code "testable". Separating responsibility helps to make your code more testable, readable and easy to maintain. In your case logic to post data over an API lies in some service which will handle api requests for your app, and you can test it separately.
Coming back to your question, I am providing you one of the possible solutions for testing async calls in your case:
// apiGateway.js
const postData = (url, data) => (
axios({
data: JSON.stringify(data),
type: 'post',
url: BASE_URL_SERVER + url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache: false
})
);
Again you can test above code separately.
// myAppApi.js
const postLoginForm = (data, callback, errorCallback) => {
return postData('/rest/login', data)
.then((response) => callback(response.data))
.catch((error) => errorCallback(error))
};
// myAppApi.test.js
// import * as myAppApi from '../myAppApi'
it('should call callback when response is successful', async () => {
const mockResponse = {};
const mockRequestData = {};
const mockSuccessCallback = jest.fn();
const mockErrorCallback = jest.fn();
spyOn(myAppApi, 'postLoginForm').and.returnValue(Promise.resolve(mockResponse));
await myAppApi.postLoginForm(mockRequestData, mockSuccessCallback, mockErrorCallback);
expect(mockSuccessCallback).toHaveBeenCalled();
});
it('should call error callback when response is failed', async () => {
const mockRequestData = {};
const mockSuccessCallback = jest.fn();
const mockErrorCallback = jest.fn();
spyOn(myAppApi, 'postLoginForm').and.returnValue(Promise.reject());
await myAppApi.postLoginForm(mockRequestData, mockSuccessCallback, mockErrorCallback);
expect(mockErrorCallback).toHaveBeenCalled();
});
In above tests you can use different mocking methods or libraries.
And finally your component will look something like this
// LoginForm.js
class LoginForm extends React.Component {
onSuccessfulLogin(responseData) {
//.. success logic here
}
onFailedLogin(error) {
//.. error logic here
}
onSubmitForm(event) {
postLoginForm(this.state.data, this.onSuccessfulLogin, this.onFailedLogin)
}
}
As you can see separating out logic helps in testing. Further it will save you from ending up with component with tons of code in it. You can test your component for its state and presentation.
Hope this answers your question!

react redux async action call next function after finishing the function

Lets say I have a functin that calls api:
export default function AuthApi({url, method, headers, data}={}){
return (dispatch, getState) => {
fetch(url, {
method: method || null,
headers: headers || null,
body: form || null,
}).then(function(response) {
return response.json();
}).then(function(response){
console.log(response)
})
}
}
Now I want to call this api somewhere in action:
dispatch(AuthApi({url: "some_url", method: "POST", data: data}))
console.log("called api")
dispatch(userInfo(response))
console.log(getState())
router.pushState(null, '/profile')
Here I am calling the api with dispatch and then dispatch(userInfo) .
I assume my dispatch(userInfo()) function to be called after all the processing in dispatch(AuthApi())
But here it goes into AuthApi() but without completing it it start to call other function or process
How can I only call my other function or logic or ever console.log() after dispatch(AuthApi()) is completely finished.
Thank you
You can use Promises, they work perfectly well with thunkMiddleware:
dispatch(AuthApi({url: "some_url", method: "POST", data: data})).then((response) => {
dispatch(userInfo(response))
})
More examples here
UPDATE
You should also modify action to return promise.
export default function AuthApi({url, method, headers, data}={}){
return (dispatch, getState) => {
return fetch(url, {
method: method || null,
headers: headers || null,
body: form || null,
}).then(function(response) {
return response.json();
})
}
}

Categories

Resources