Set State inside axios post method - javascript

I have axios post method to check username and password when they available I want to set a state as true but always get the state as false.
My code is:
const [validated, setValidated] = useState(false);
const login = () => {
Axios.post('http://124.43.17.60:4000/loginnew', {
username: email,
password: password
}).then((response) => {
// let data = response.data
console.log("my web response all", response)
console.log("my web response", response.data[0])
try {
if (response.data[0].role) {
let role = response.data[0].role
setValidated(true)
console.log(validated)
if (role == "admin") {
history.push('/admin')
} else {
history.push('/member')
}
}
} catch (err) {
alert.error("wrong credentials");
}
});
}
Can anyone tell me what um doing wrong and please help me to solve this problem

In React, modifying the state is an asynchronous action. Meaning that this piece of code won't give your expected results:
setValidated(true)
console.log(validated) // Won't be updated to true
If you want to achieve something specifically on true, you have 2 options
1. useEffect
useEffect(() => { if (validated) { ...do something... } }, [validated])
2. setState callback
setValidated((oldValidated) => {
const newValidated = !oldValidated;
console.log(newValidated) // true if oldValidated was false
// do something with newValidated === true
return newValidated; // Make sure to return the new value, otherwise state won't update at all.
})

You cannot view the "updated" state inside you login function as setValidated is an asynchronous process, which mean the code will not wait for the data to get back and still use the old value. Read this post for more explanation
If you want to view the "updated" value of validated you need to create a useEffect like so
useEffect(() => {
console.log(validated)
})

useEffect(() => {
Axios.post('http://124.43.17.60:4000/loginnew', {
username: email,
password: password
}).then((response) => {
// let data = response.data
console.log("my web response all", response)
console.log("my web response", response.data[0])
try {
if (response.data[0].role) {
let role = response.data[0].role
setValidated(true)
console.log(validated)
if (role == "admin") {
history.push('/admin')
} else {
history.push('/member')
}
}
} catch (err) {
alert.error("wrong credentials");
}
});
}, []);

Related

Redux store not being populated when mocking API call

I wrote a Register component in react, it is a simple form that on submit will post to an API. The call to the API will return an object with certain data, this data will be then added to the redux store.
I wrote some tests for this. I'm using Mock Service Worker (MSW) to mock the API call. This is my first time for writing these kind of tests so I'm not sure if I'm doing anything wrong, but my understanding was that MSW would intercept the call to the API and return whatever I specify in the MSW config, after that it should follow the regular flow.
Here's my reducer:
const authReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case actionTypes.REGISTER_NEW_USER:
const newUser = new User().register(
action.payload.email,
action.payload.firstName,
action.payload.lastName,
action.payload.password
)
console.log("User registered data back:");
console.log(newUser);
return {
...state,
'user': newUser
}
default:
return state;
}
}
this is my User class where the actual call is performed:
import axios from "axios";
import { REGISTER_API_ENDPOINT } from "../../api";
export default class User {
/**
* Creates a new user in the system
*
* #param {string} email - user's email address
* #param {string} firstName - user's first name
* #param {string} lastName - user's last name
* #param {string} password - user's email address
*/
register(email, firstName, lastName, password) {
// console.log("registering...")
axios.post(REGISTER_API_ENDPOINT, {
email,
firstName,
lastName,
password
})
.then(function (response) {
return {
'email': response.data.email,
'token': response.data.token,
'active': response.data.active,
'loggedIn': response.data.loggedIn,
}
})
.catch(function (error) {
console.log('error');
console.log(error);
});
}
}
this is my action creator:
export function createNewUser(userData) {
return {
type: REGISTER_NEW_USER,
payload: userData
}
}
this is the onSubmit method in my Register component:
const onSubmit = data => {
// console.log(data);
if (data.password !== data.confirmPassword) {
console.log("Invalid password")
setError('password', {
type: "password",
message: "Passwords don't match"
})
return;
}
// if we got up to this point we don't need to submit the password confirmation
// todo but we might wanna pass it all the way through to the backend TBD
delete data.confirmPassword
dispatch(createNewUser(data))
}
and this is my actual test:
describe('Register page functionality', () => {
const server = setupServer(
rest.post(REGISTER_API_ENDPOINT, (req, res, ctx) => {
console.log("HERE in mock server call")
// Respond with a mocked user object
return res(
ctx.status(200),
ctx.json({
'email': faker.internet.email(),
'token': faker.datatype.uuid(),
'active': true,
'loggedIn': true,
}))
})
)
// Enable API mocking before tests
beforeEach(() => server.listen());
// Reset any runtime request handlers we may add during the tests.
afterEach(() => server.resetHandlers())
// Disable API mocking after the tests are done.
afterAll(() => server.close())
it('should perform an api call for successful registration', async () => {
// generate random data to be used in the form
const email = faker.internet.email();
const firstName = faker.name.firstName();
const lastName = faker.name.lastName();
const password = faker.internet.password();
// Render the form
const { store } = renderWithRedux(<Register />);
// Add values to the required input fields
const emailInput = screen.getByTestId('email-input')
userEvent.type(emailInput, email);
const firstNameInput = screen.getByTestId('first-name-input');
userEvent.type(firstNameInput, firstName);
const lastNameInput = screen.getByTestId('last-name-input');
userEvent.type(lastNameInput, lastName);
const passwordInput = screen.getByTestId('password-input');
userEvent.type(passwordInput, password);
const confirmPasswordInput = screen.getByTestId('confirm-password-input');
userEvent.type(confirmPasswordInput, password);
// Click on the Submit button
await act(async () => {
userEvent.click(screen.getByTestId('register-submit-button'));
// verify the store was populated
console.log(await store.getState())
});
});
So I was expecting my call to be intercepted whenever the REGISTER_API_ENDPOINT url is detected, and the value of the mocked call to be added to my redux state instead of the value of the actual API call in register method but that doesn't seem to be happening. If that's not the way to test a value in the store, how else can I achieve that?
So at the end of my test, when printing the store I was expecting to see:
{ auth: { user:
{
'email': faker.internet.email(),
'token': faker.datatype.uuid(),
'active': true,
'loggedIn': true,
}
}
but instead I'm seeing:
{ auth: { user: null } }
Is this the right approach for this test?
Thanks
EDIT
Doing some refactoring based on the comments. Now my onSubmit method looks like:
const onSubmit = async data => {
if (data.password !== data.confirmPassword) {
console.log("Invalid password")
setError('password', {
type: "password",
message: "Passwords don't match"
})
return;
}
// if we got up to this point we don't need to submit the password confirmation
// todo but we might wanna pass it all the way through to the backend TBD
delete data.confirmPassword
let user = new User()
await user.register(data).
then(
data => {
// console.log("Response:")
// console.log(data)
// create cookies
cookie.set("user", data.email);
cookie.set("token", data.token);
dispatch(createNewUser(data))
}
).catch(err => console.log(err))
Notice that now I'm dispatching the response from User.register in here instead of doing it in User.register. Also notice that this function is now async and await for the register function call to be finalized, at that moment it'll populate the store.
The register method now looks like the following:
async register(data) {
let res = await axios.post(REGISTER_API_ENDPOINT, {
'email': data.email,
'firstName': data.firstName,
'lastName': data.lastName,
'password': data.password
})
.then(function (response) {
return response
})
.catch(function (error) {
console.log('error');
console.log(error);
});
return await res.data;
}
now it's only in charge of performing the API call and returning the response.
The reducer was also simplified not to have any side effect changes, so it looks like:
const authReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case actionTypes.REGISTER_NEW_USER:
const newUser = action.payload
return {
...state,
'user': newUser
}
default:
return state;
}
}
my test is mostly the same, the only difference is the part where I'm inspecting the store value:
// Click on the Submit button
await act(async () => {
userEvent.click(screen.getByTestId('register-submit-button'));
});
await waitFor(() => {
// verify the store was populated
console.log("Store:")
console.log(store.getState())
})
Now, this sometimes work and sometimes does not. Meaning, sometimes I get correct store printed as follows:
console.log
Store:
at test/pages/Register.test.js:219:21
console.log
{
auth: {
user: {
email: 'Selena.Tremblay#hotmail.com',
token: '1a0fadc7-7c13-433b-b86d-368b4e2311eb',
active: true,
loggedIn: true
}
}
}
at test/pages/Register.test.js:220:21
but sometimes I'm getting null:
console.log
Store:
at test/pages/Register.test.js:219:21
console.log
{ auth: { user: null } }
at test/pages/Register.test.js:220:21
I guess I'm missing some async code somewhere but I cannot put a pin on where is it.
There are some Redux rules that are being broken here:
Don't do side effects in reducers:
reducers should be pure functions: for the same input, return always
the same output. This is not the place to do API calls.
State should be immutable: you should never change a state value by reference, always provide a new state with a new object containing the changes.
So, the classical redux approach would be to have three actions in Redux: REGISTER_USER, REGISTER_USER_SUCCEEDED, REGISTER_USER_FAILED .
reducer:
const authReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case actionTypes.REGISTER_USER:
return {
...state,
status: 'loading'
}
case actionTypes.REGISTER_USER_SUCCEEDED:
return {
...state,
status: 'idle',
user: action.user
}
case actionTypes.REGISTER_USER_FAILED:
return {
...state,
status: 'error'
}
default:
return state;
}
}
Then, async work should be done in your event handlers:
onSubmit:
const onSubmit = async data => {
// ...
dispatch(registerNewUser());
const user = new User()
try {
await user.register(data);
dispatch(registerNewUserSucceeded(user));
} catch(e) {
console.error(e);
dispatch(registerNewUserFailed());
}
}
**Don't forget to return the promise from axios inside your register function, so you can await on the promise. Currently, you are only calling axios, but not updating or returning anything...
What's great about this, is that testing your store doesn't require you to do any network calls! You could ditch MSW (although it's a great lib, just not needed here).
In your tests, just check your store state before and after every transition:
const mockUser = {...} // provide a mock user for your test
const store = createStore(authReducer);
store.dispatch(registerNewUserSucceeded(mockUser);
expect(store.getState()).toEqual({user: mockUser, status: 'idle'});
Edit
In response to the asker's edit, there is now a bug because of the confusing combination of await with .then.
Specifically, in onSubmit, you are doing both await and .then on the same promise. In this case, there is a race condition. The .then call happens first, and after that the await happens.
So instead of await user.register(data).then(...):
const onSubmit = async data => {
// ...
try {
await user.register(data);
} catch(e) {
console.log(e);
}
dispatch(createNewUser(data));
}
Here I'm only using await. the try/catch clause is instead of calling .catch on the promise.
using await lets you write as if you are writing synchronic code, so just write whatever you would put inside .then on the next line after an await expression.
Also in your register function:
async register(data) {
try {
let res = await axios.post(...);
return res;
} catch(e) {
console.log("error: ", e);
}
}
The state won't be updated instantly, as the server call is a promise. You should await something on the page the indicates the process is complete like this:
// Click on the Submit button
await act(async () => {
userEvent.click(screen.getByTestId('register-submit-button'));
await wait(() => getByText('Some text that appears after success '));
// verify the store was populated
console.log(await store.getState())
});
Or you can wait for the update:
// Click on the Submit button
await act(async () => {
userEvent.click(screen.getByTestId('register-submit-button'));
await act(() => sleep(500));
// verify the store was populated
console.log(await store.getState())
});

Vue JS/Vuex - Call to edit user not saving

I'm trying to make an api call that allows me to edit a single user. The issue that I'm experiencing is that despite the call being successful (and no errors appearing), the changes are not saving. Can someone kindly guide me as to what I'm doing wrong exactly, please? I feel that I'm missing a function that allows me to save the changes after I make the call, but I'm not entirely sure how to go about this.
Edit user details:
setup() {
const store = vuexStore;
const adminId = router.currentRoute.params.adminId;
/** Edit **/
function editUser(formData) {
formData.adminId = adminId;
editAdminAccount(formData).then(response => {
if (response) {
redirectUserTo(ROUTE_NAMES_ADMIN.ADMIN_ACCOUNTS);
saveUserChanges(formData);
}
})
}
// Action
function editAdminAccount(data) {
return store.dispatch(UPDATE_ADMIN_ACCOUNT, data);
}
getSelectedAdmin(adminId);
const selectedAdmin = computed(() => store.getters.getSelectedAdmin);
function getSelectedAdmin(adminId) {
return store.dispatch(GET_ADMIN_BY_ID, adminId)
}
return {
editUser,
selectedAdmin,
}
}
Actions:
updateAdminAccount({commit}, payload) {
let formData = new FormData()
formData.append('email', payload.email)
formData.append('name', payload.name)
formData.append('password', payload.password);
return apiHandler.put(`user/admin/${payload.adminId}`, formData, apiHandler.getAuthHeader()).then(response => {
return !!apiHandler.isSuccess(response.status);
}).catch(error => {
commit(SET_API_ERROR, error);
});
},
You should maybe check what the api call is returning with some console.logs to be sure of the data that is sent back.
Nevertheless, do not have to work with formdata, you can send your query items directly :
updateAdminAccount({commit}, payload) {
return apiHandler.put(`user/admin/${payload.adminId}`, payload, apiHandler.getAuthHeader())
.then(response => !!apiHandler.isSuccess(response.status))
.catch(error => commit(SET_API_ERROR, error));
}
You also should edit the user directly after the api call in the action, and not from the template. So that the logic is kept at one place :
updateAdminAccount({commit}, payload) {
return apiHandler.put(`user/admin/${payload.adminId}`, payload, apiHandler.getAuthHeader())
.then(response => {
if (!!apiHandler.isSuccess(response.status)) {
commit('UPDATE_ADMIN', payload) // payload or response.data depending if api is returning edited object
}
return !!apiHandler.isSuccess(response.status)
})
.catch(error => commit(SET_API_ERROR, error));
}

Separating Mongoose code from Express Router

So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {

React Formik not handling errors properly

I'm using the basic Formik template to work on a Login Form.
onSubmit={(
values,
{ setSubmitting, setErrors /* setValues and other goodies */ }
) => {
props.logMeIn(values);
// LoginToSystem(values).then(
// user => {
// setSubmitting(false);
// // do whatevs...
// // props.updateUser(user)
// },
// errors => {
// setSubmitting(false);
// // Maybe transform your API's errors into the same shape as Formik's
// //setErrors(transformMyApiErrors(errors));
// console.log(errors);
// }
// );
}}
This problem is within the onSubmit section; The demo code is commented out but it uses a LoginToSystem function that seems to be a promise. I can not figure out 'what' this function is supposed to me. My function that handles this would be props.logMeIn() - Which also does not work as intended
If the login is successful, it will currently work as expected, and everything is fine. However, if the login fails (404, 401, whatever) the form will remain there, and the setSubmitting log stays there so Submit is grayed out but nothing is done.
If I try to replace LoginToSystem with my function, I get an error on the .then that I can't perform .then on undefined.
I'm wondering if perhaps this is because my function is not set up like a Promise?
loginClickHandler = (user) => {
let userObj = {
email: user.email,
password: user.password
}
axios.post('api/v1/auth/sign_in', userObj)
.then((res) => {
console.log(res.headers);
let loggedInUser = {
'access_token': res.headers['access-token'],
'client': res.headers['client'],
'uid':res.headers['uid'],
'signedIn': true
};
this.setState({
user: loggedInUser
})
this.props.retrieve(user.email);
})
.catch((err) => {
console.log(err);
return err
})
};
My function does properly catch (Thanks to axios) on the .then/.catch, but perhaps I am supposed to modify those to provide a callback so that onSubmit can properly fire?
With some guidance I was able to resolve this one simpler. Axios is natively returning a 'promise' so I just needed to ensure the outcome of the function was axios' method in the end.
loginClickHandler = (user) => {
let userObj = {
email: user.email,
password: user.password
}
const request = axios.post('api/v1/auth/sign_in', userObj);
request.then((res) => {
console.log(res.headers);
let loggedInUser = {
'access_token': res.headers['access-token'],
'client': res.headers['client'],
'uid': res.headers['uid'],
'signedIn': true
};
this.setState({user: loggedInUser, auth: true, anchorEl: null})
}).catch((err) => {
console.log(err);
// setErrors({ test: 'This was an error' })
})
return request;
};
In onSubmit there's a second argument for setting your errors. I added flow to be able to see the types better in this answer for you.
<Formik
initialValues={...}
... // Other Props
onSubmit={this.handleSubmit} // This is where you handle your login logic
render={this.renderForm} // Render your form here.
You have a callback to help you set errors in the second argument
handleSubmit = (
user: FormValues,
{ setErrors }: FormikActions<FormValues>
) => {
return axios.post('api/v1/auth/sign_in', userObj)
...
.catch(e) => {
setErrors({ username: 'Invalid Username' }) // Object you want to put here.
}
}
In your render form function you now have errors that you can use based on what you called in your setErrors
renderForm = ({
... // These are your other FormikProps you're using
errors // You want to use this
}: FormikProps<FormValues>) => (
... // Your rendering here
// errors.username
)
For flow types on Formik
https://github.com/flowtype/flow-typed/blob/master/definitions/npm/formik_v0.9.x/flow_v0.53.x-/formik_v0.9.x.js

Using continue as button for facebook login

I am building a React Native app, I previously implemented Facebook login using login Manager
export const onLogin = () => {
return (dispatch) => {
console.log('inside login');
dispatch({ type: ON_LOGIN });
LoginManager.logInWithReadPermissions(['public_profile',
'email']).then((res) => {
console.log(res);
MakeGraphRequest(dispatch);
},
(error) => {
console.log(error);
LoginFail(dispatch, error);
});
};
};
function MakeGraphRequest(dispatch) {
const responseInfoCallback = (error: ?Object, result: ?Object) => {
if (error) {
console.log(error);
LoginFail(dispatch, error);
} else {
axios({
method: 'post',
url: 'url',
data: {
first_name: result.first_name,
last_name: result.last_name,
profile_photo: result.picture.data.url,
email: result.email,
spend_history: []
}
}).then((res) => {
if (res.data.userid) {
const userid = res.data.userid;
LoginSuccessForUnregisteredUser(dispatch, result, userid);
} else {
LoginSuccess(dispatch, result);
}
});
}
};
const infoRequest = new GraphRequest(
'/me',
{
parameters: {
fields: {
string: 'email, first_name, last_name, picture.type(large), birthday'
}
}
},
responseInfoCallback
);
new GraphRequestManager().addRequest(infoRequest).start();
}
Also I've used Login Button and Expo Facebook login but I could not find a way to implement this kind of a login.
Should I use Login Manager or Login Button. The Facebook docs are valid for web only. Is there a way to integrate this in my RN(react native) project?
You already have the user data in the response. So you can just start your screen (like in the picture) and ask if the user really wants to sign in with this account. Only after that, call your LoginSuccess events. If the user doesn't want to login just dispose the result data.
.then((res) => {
if (res.data.userid) {
const userid = res.data.userid;
// add screen logic here
// LoginSuccessForUnregisteredUser(dispatch, result, userid);
} else {
// add screen logic here
// LoginSuccess(dispatch, result);
}
});
Same would go with the Facebook Login Button or AuthSession.
Using AsyncStorage to save/fetch the state and get wether he goes or goes not to the "continue as" screen.
try {
await AsyncStorage.setItem('#MySuperStore:key', 'I like to save it.');
} catch (error) {
// Error saving data
}
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
if (value !== null){
// We have data!!
// show "continue as" screen
console.log(value);
}
} catch (error) {
// Error retrieving data
}

Categories