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
Related
I am curious how to reset a q-form when the submit action is triggered. I run a function onSubmit but I am not sure how in that method to reset the q-form without having to do each field individually which is annoying. Here is my code:
//methods
const onSubmit = (event) => {
let jsonData =
{
FirstName: firstName.value,
LastName: lastName.value,
PhoneNumber: phoneNumber.value,
EmailAddress: emailAddress.value,
Message: message.value,
Token: token.value
}
api.post('/api/contactus', jsonData)
.then((response) => {
})
.catch(() => {
console.log('API request failed')
})
}
The documentation has an example of exactly what you want
// <q-form ref="myForm">
// to reset validations:
function reset () {
myForm.value.resetValidation()
}
or Options API:
this.$refs.myForm.resetValidation()
This function is shown in the documentation as being tied to a "Reset" button but there's no reason you can't use it after submitting as well.
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())
});
I use MERN stack and redux. I have two problem and please help me.
1) Every component react I add this:
const user = useSelector( state => state.user );
useEffect( ()=>{
dispatch(User_Auth(12)) ; // I write 12 for action work.
});
I want to get user data every time if user loginned or not. Is it true? or some idea have?
2) In backend if data current I send using 200 status codes. another variant I send data other status like this:
router.get('/auth', (req, res) => {
if(req.isAuthenticated()){
req.user.isAuth = true;
res.status(200).json(req.user);
}
else{
return res.status(401).json({
isAuth: false
});
}
});
This is my action get User data:
export const User_Auth = (value) => async (dispatch) => {
value = value + 0;
await axios({
method: "GET",
url:'http://localhost:3001/api/users/auth',
withCredentials:true
})
.then(res => {
dispatch({type: user_auth, payload: res.data});
}).catch(error => {
// console.log("Auth geldim: ", error);
});
}
I want if cannot see errors in console.log browser. Can I do that?
Thanks
If you want to see the status code from an error you have to access it like this
error.status
And to get the message
error.message
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) => {
I am relatively new to using Promises and MongoDB / Mongoose, and am trying to chain a flow through several database queries into an efficient and reliable function.
I want to know if my final function is a good and reliable way of achieving what I want, or if there are any issues or any improvements that can be made.
The process is as follows:
1) Check whether or not the user already exists
usersSchema.findOne({
email: email
}).then(res => {
if(res==null){
// user does not exist
}
}).catch(err => {});
2) Add the new user to the database
var new_user = new usersSchema({ email: email });
new_user.save().then(res => {
// new user id is res._id
}).catch(err => {});
3) Assign a free promotional code to the user
codeSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(res._id)
}).then(res => {
// user's code is res.code
}).catch(err => {});
Obviously, each query needs to execute in sequence, so after a lot of research and experimentation into how to do this I have combined the queries into the following function, which seems to be working fine so far:
function signup(email){
// check email isn't already signed up
return usersSchema.findOne({
email: email
}).then(res => {
if(res==null){
// add to schema
var new_user = new usersSchema({ email: email });
// insert new user
return new_user.save().then(res => {
var result = parse_result(res);
// assign a code
return codesSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(result._id),
});
});
}else{
return 'The user already exists';
}
});
}
signup('test#test.com').then(res => {
console.log('success, your code is '+res.code);
}).catch(err => {
console.log(err);
});
I'm still trying to get my head around exactly how and why this works - the function is returning a promise, and each nested promise is returning a promise.
My main concerns are that there is a lot of nesting going on (is there perhaps a way to do this by chaining .then() callbacks instead of nesting everything?) and that the nested promises don't appear to have error catching, although as the signup() function itself is a promise this seems to catch all the errors.
Is anyone knowledgeable on the subject able to confirm whether my process looks good and reliable or not? Thanks!
To avoid indentation-hell, if you return a value from the function passed to a Promise's .then()-method, you can chain multiple .then() as a neat and tidy pipeline. Note that you can also return a Promise that has pending status, and then next function in line will execute when it has resolved.
function signup (email) {
return usersSchema.findOne({
email: email
}).then(res => {
if (res) throw 'The user already exists'
var new_user = new usersSchema({ email: email })
return new_user.save()
}).then(res => {
var result = parse_result(res)
return codesSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(result._id)
})
})
}
Even better, if you have the possibility to use async/await (Node v7.6 or above), your code can look like normal blocking code:
async function signup (email) {
let user = await usersSchema.findOne({ email: email })
if (user) throw 'The user already exists'
let new_user = await new usersSchema({ email: email }).save()
let result = parse_result(new_user)
return codesSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(result._id)
})
}
Your original function call code works on both without changes.
you code can be improve in this way
function signup(email){
// check email isn't already signed up
return usersSchema.findOne({
email: email
}).then(res => {
if(res==null){ // add to schema
var new_user = new usersSchema({ email: email });
// insert new user
return new_user.save()
}else{
return Promise.reject(new Error('The user already exists'));
}
})
.then(res => {
var result = parse_result(res);
// assign a code
return codesSchema.findOneAndUpdate({used: false,user_id: true},{used: true,user_id: mongoose.Types.ObjectId(result._id),});
});
}
signup('test#test.com').then(res => {
console.log('success, your code is '+res.code);
}).catch(err => {
console.log(err);
});