I am trying to perform a Login with JSON web tokens. Right now I am doing a simple thing, I just want to give a username and a password and, if they are correct ,as a response I want a console log with the token.
Instead of receiving a token in the console, all I get is the http-address changing from http:/localhost:3000/login to http:/localhost:3000/login?username=username&password=password
Can somebody explain me why does that happen?
Here is my code:
import React, {useState} from 'react';
import {gql} from 'graphql-tag';
import {Mutation} from 'react-apollo';
import {Button, TextField} from '#material-ui/core';
const LOGIN = gql`
mutation Login($username: String!, $password:String!){
login(username:$username, password:$password){
token
}
}
`;
export default function Login(){
const [formState, setFormState] = useState({
values:{}
})
};
const handleChange = event => {
event.persist();
setFormState( formState => ({
...formState,
values:{
...formState.values,
[event.target.name]:event.target.value
}
}));
};
const handleSubmit = async(event, login)=>{
event.preventDefault();
console.log(formState.values)
login().then(async({data}) => {
localStorage.setItem('token', data.login.token);
})
.catch(function(e){
console.log('Something went wrong');
});
console.log(localStorage.getItem('token'));
}
return (
<Mutation mutation={LOGIN} variables={{username:formState.values.username, password: formState.values.password }}>
{(login, {data}) => (
<div>
<form className="form" onSubmit={handleSubmit, login}>
<TextField
label="Username"
name="username"
onChange={handleChange}
value=formState.values.username
></TextField>
<TextField
label="Password"
name="password"
onChange={handleChange}
value=formState.values.password
></TextField>
<Button type="submit">Login</Button>
</form>
</div>
)}</Mutation>
);
I would like to know where my error is, I am new to React and Javascript
Related
component.tsx
import React, { ChangeEvent, FormEvent, useEffect, useState } from "react";
import { Form, FormControl, FormGroup, FormLabel } from "react-bootstrap";
import Button from "react-bootstrap/Button";
import Card from "react-bootstrap/Card";
import { useHistory } from "react-router-dom";
import { StorageKeys } from "../ProtectedRoute";
import "./styles.scss";
const Login = () => {
const history = useHistory();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSetEmail = (event: ChangeEvent<HTMLInputElement>) =>
setEmail(event.target.value);
const handleSetPassword = (event: ChangeEvent<HTMLInputElement>) =>
setPassword(event.target.value);
const handleSubmit = (event: FormEvent<HTMLElement>) => {
event.preventDefault();
console.log("email::", email);
console.log("password::", password);
localStorage.setItem(StorageKeys.TOKEN, "TODO: Auth");
history.push("/");
};
useEffect(() => {
localStorage.removeItem(StorageKeys.TOKEN);
}, []);
return (
<div id="login">
<Card id="loginCard">
<Card.Header>Login</Card.Header>
<Card.Body>
<Form onSubmit={handleSubmit}>
<FormGroup>
<FormLabel>Email address</FormLabel>
<FormControl type="email" id="email" placeholder="Enter email"
value={email} onChange={handleSetEmail}
required={true} />
</FormGroup>
<FormGroup>
<FormLabel>Password</FormLabel>
<FormControl type="password" id="password" placeholder="Password"
value={password} onChange={handleSetPassword}
required={true} />
</FormGroup>
<div className={"button-container"}>
<Button id="submit" variant="primary" type="submit">
Submit
</Button>
</div>
</Form>
</Card.Body>
</Card>
</div>
);
};
export default Login;
This works when using shallow to render the component:
login.test.tsx
import { mount, shallow } from "enzyme";
import React from "react";
import Login from "./index";
describe("Login Component", () => {
test("can properly submit form", () => {
jest.spyOn(window.localStorage.__proto__, "removeItem");
const wrapper = shallow(<Login />);
// This works just find, finds only the one #email input.
const emailInput = wrapper.find("#email");
emailInput.simulate("change", { target: { value: testLoginData.email } });
});
});
When using mount this throws the error:
Error: Method “simulate” is meant to be run on 1 node. 2 found instead
import { mount, shallow } from "enzyme";
import React from "react";
import Login from "./index";
describe("Login Component", () => {
test("can properly submit form", () => {
jest.spyOn(window.localStorage.__proto__, "removeItem");
const wrapper = mount(<Login />);
const emailInput = wrapper.find("#email");
// This will now complain about there being too many nodes.
emailInput.simulate("change", { target: { value: testLoginData.email } });
});
});
What gives? I need to use mount for the test that I'm working on, why is it finding multiple elements when there is for sure ONLY ONE.
I can patch it to work using the following, but I shouldn't have to... right?!
emailInput.at(0).simulate("change", { target: { value: testLoginData.email } });
So it's because your <FormControl is the first with this id and <input is second(or vice versa).
There are wide list of approaches:
.at(0) will work, but this way you will never know if you(because of error in the code) renders multiple elements. It might happen if conditions in conditional rendering {someFlag && <.... that suppose to be mutually exclusive are not. So really, it's a bad way.
Mock FormControl to be final element - so <input will not be returned anymore by .find()(honestly never used that, just assume it will work - but still looks messy and need additional boilerplate code for each test file, so not really handful way):
jest.mock('../FormControl.jsx', () => null);
use hostNodes() to filter only native elements(like <span> to be returned):
const emailInput = wrapper.find("#email").hostNodes();
I vote for 3rd option as most reliable and still safe for catching code logic's errors.
I using next.js with swr my code is:
import { Typography } from '#material-ui/core';
import { Formik, Form, Field} from 'formik'
import useSWR, { mutate } from 'swr';
import { myRequest } from '../libs/auth';
import Axios from 'axios';
interface Props {
username: string;
password: string;
message: string;
}
const LoginForm: React.FC<Props> = (loginData: Props)=>{
const login_req = new myRequest('post', loginData)
const {data} = useSWR('/api/user/login', login_req.send) //delete these line no errors
return (
<>
<Typography variant="h3">
Login
</Typography>
<Formik onSubmit ={async(values, formikHelpers) =>{
mutate('/api/user/login', [...data, values], false) //delete these line no errors
await Axios.post('/api/user/login', values)
alert(JSON.stringify(values))
formikHelpers.setSubmitting(false)
}}
initialValues={{ username: '', password:'' }}
>
<Form>
<label htmlFor="message"></label>
<label htmlFor="username">email:</label>
<Field name="username" ref ></Field>
<label htmlFor="password">password:</label>
<Field name='password' type="password"></Field>
<button type="submit">Submit</button>
</Form>
</Formik>
</>
)
}
export default LoginForm
the error is:
TypeError: Cannot read property 'suppressHydrationWarning' of null
I think that is my request will sending null as response for people do not initialization the username at the loading time.
Is there any way to deal with empty input of swr hook?
I am having a painful moment because of some small issue in my react app.
import React, { useState } from 'react';
import { Box } from '#material-ui/core';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
import { HeadTwo, Text, StdLink } from '../../styled-components/Text';
import { ContainedBtn } from '../../styled-components/Button';
import { TextField } from '../../styled-components/Input';
import { FlexCenter, FlexStart, Form } from '../../styled-components/Layout';
import { login } from '../../redux/auth/actions';
const SignIn = ({ login, history }) => {
const [form, setForm] = useState({
email: '',
password: '',
});
const handleChange = e => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const handleSubmit = e => {
e.preventDefault();
login(form, history);
};
return (
<FlexCenter>
<Form onSubmit={e => handleSubmit(e)} width="45rem" mt="20px" mb="20px">
<FlexStart mb={2} borderBottom={2} borderColor="common.dark">
<HeadTwo sz="2.6rem">Sign In</HeadTwo>
</FlexStart>
<TextField mb={2} hidelabel={form.email.length > 0 ? 'none' : null}>
<input
onChange={handleChange}
value={form.email}
type="email"
name="email"
placeholder="email"
id="email"
/>
</TextField>
<TextField mb={2} hidelabel={form.password.length > 0 ? 'none' : null}>
<input
onChange={handleChange}
value={form.password}
type="password"
placeholder="password"
name="password"
id="password"
/>
</TextField>
<Box mb={1}>
<ContainedBtn bg="#000" cr="#fff">
LOGIN
</ContainedBtn>
</Box>
<FlexCenter mb={1}>
<Text> Don't have an account? </Text>
<Box ml={1}>
<StdLink to="/register">register</StdLink>
</Box>
</FlexCenter>
<FlexCenter mb={1}>
<Text> Forget your password?</Text>
<Box ml={1}>
<StdLink>recover</StdLink>
</Box>
</FlexCenter>
</Form>
</FlexCenter>
);
};
SignIn.propTypes = {
login: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
};
export default connect(
null,
{ login }
)(SignIn);
so this is my signIn component and login function is yelling at me saying 'login is already declared in upper scope', which is quite weird because login prop comes from connect fn right?
anyway, So I tried changing eslint rule like this
{
"rules": {
"no-shadow": [
"error",
{ "builtinGlobals": false, "hoist": "never", "allow": [] }
]
}
}
since I set hoist to never, the warning should be gone, but it still remained.
does anyone know what I did wrong?
thanks !!
The login function that is imported will not work since you have parameter with the same name. You just need to rename:
const SignIn = ({ login: signin, history }) => {
// now the imported login will work
// to use login parameter, you now have signin
I am looking to fire a submit handler for a LoginForm. However, for some reason, instead of my mock function being called, the actual handler for the component gets fired (calling an external api). How can I ensure that my mock handler gets called instead?
The three components of interest are below (The presentational, container and the test suite)
LoginForm.js
import { Formik, Form, Field } from 'formik';
import { CustomInput } from '..';
const LoginForm = ({ initialValues, handleSubmit, validate }) => {
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={handleSubmit}
>
{({ isSubmitting, handleSubmit }) => {
return (
<Form onSubmit={handleSubmit}>
<div className="d-flex flex-column justify-content-center align-items-center">
<Field
data-testid="usernameOrEmail"
type="text"
name="identifier"
placeholder="Username/Email"
component={CustomInput}
inputClass="mb-4 mt-2 text-monospace"
/>
<Field
data-testid="login-password"
type="password"
name="password"
placeholder="Password"
component={CustomInput}
inputClass="mb-4 mt-4 text-monospace"
/>
<button
data-testid="login-button"
className="btn btn-primary btn-lg mt-3 text-monospace"
type="submit"
disabled={isSubmitting}
style={{ textTransform: 'uppercase', minWidth: '12rem' }}
>
Submit
</button>
</div>
</Form>
)}}
</Formik>
);
};
export default LoginForm;
LoginPage.js
import React, { useContext } from 'react';
import { loginUser } from '../../services';
import { userContext } from '../../contexts';
import { loginValidator } from '../../helpers';
import { setAuthorizationToken, renderAlert } from '../../utils';
import LoginForm from './login-form';
const INITIAL_VALUES = { identifier: '', password: '' };
const LoginPage = props => {
const { handleUserData, handleAuthStatus } = useContext(userContext);
const handleSubmit = async (values, { setSubmitting }) => {
try {
const result = await loginUser(values);
handleAuthStatus(true);
handleUserData(result.data);
setAuthorizationToken(result.data.token);
props.history.push('/habits');
renderAlert('success', 'Login Successful');
} catch (err) {
renderAlert('error', err.message);
}
setSubmitting(false);
};
return (
<LoginForm
initialValues={INITIAL_VALUES}
validate={values => loginValidator(values)}
handleSubmit={handleSubmit}
/>
);
};
export default LoginPage;
LoginPage.spec.js
import React from 'react';
import { cleanup, getByTestId, fireEvent, wait } from 'react-testing-library';
import { renderWithRouter } from '../../../helpers';
import LoginPage from '../login-page';
afterEach(cleanup);
const handleSubmit = jest.fn();
test('<LoginPage /> renders with blank fields', () => {
const { container } = renderWithRouter(<LoginPage />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});
test('Clicking the submit button after entering values', async () => {
const { container } = renderWithRouter(<LoginPage handleSubmit={handleSubmit} />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
fireEvent.change(usernameOrEmailNode, { target: { value: fakeUser.username }});
fireEvent.change(passwordNode, { target: { value: fakeUser.password }});
fireEvent.click(submitButtonNode);
await wait(() => {
expect(handleSubmit).toHaveBeenCalledTimes(1);
});
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});```
To answer your question, you will need to first make the handleSubmit constant accessible outside LoginPage.js so that it may be mocked and then tested. For example,
LoginPage.js
export const handleSubmit = async (values, { setSubmitting }) => {
... code to handle submission
})
And in your tests - LoginPage.spec.js
jest.unmock('./login-page');
import LoginPage, otherFunctions from '../login-page'
otherFunctions.handleSubmit = jest.fn();
...
test('Clicking the submit button after entering values', () => {
...
fireEvent.click(submitButtonNode);
expect(handleSubmit).toHaveBeenCalledTimes(1);
})
I hope the above fixes your problem.
But, going by the philosophy of unit testing, the above components
must not be tested the way you are doing it. Instead your test setup
should be like this -
Add a new test file called LoginForm.spec.js that tests your LoginForm component. You would test the following in this -
Check if all input fields have been rendered.
Check if the correct handler is called on submit and with the correct parameters.
The existing test file called LoginPage.spec.js would then only test if the particular form was rendered and then you could also test
what the handleSubmit method does individually.
I believe the above would make your tests more clearer and readable
too, because of the separation of concerns and would also allow you to
test more edge cases.
I recently added redux-forms to my React/Redux/Redux-thunk project, and now if I submit information to a redux-thunk action, the info is submitted successfully, but nothing after the return function fires.
Everything was working as intended before adding redux-forms, so I think thatś the source of the problem, but even after double checking the docs for Redux, redux-form, and redux-thunk, I can't find any obvious errors in my connections or setup. What am I missing?
My reducer:
import {combineReducers} from 'redux';
import {reducer as formReducer} from 'redux-form';
import signUpReducer from './containers/SignUp/reducer';
export default function createReducer() {
return combineReducers({
signUpReducer,
form: formReducer
});
}
My form component:
import React from 'react';
import {Field, reduxForm} from 'redux-form';
import {validate, onHandleInfoSubmit} from '../../containers/SignUp/actions';
import {inputField} from '../../components/SmallUIBits/FormFields';
let UserSignUpForm = props => {
const {handleSubmit} = props;
return (
<form className="NewAccountForm" onSubmit={handleSubmit}>
<div className="text-center">
<small className="center-align">All fields are required</small>
</div>
<div className="AccountLine form-group">
<Field classes="LoginInput form-control form-control-sm"
component={inputField}
label="Email address"
name="email"
placeholder="Enter email"
required="true"
type="text"
value={props.email} />
</div>
<div className="form-row">
<div className="col-lg-6 col-md-6 col-xs-12">
<Field aria-describedby="passwordHelp"
classes="LoginInput form-control form-control-sm"
component={inputField}
label="Password"
name="password"
placeholder="Password"
required="true"
type="password"
value={props.password} />
<div className="col-lg-6 col-md-6 col-xs-12">
<Field classes="LoginInput form-control form-control-sm"
component={inputField}
label="Confirm password"
name="passwordConfirm"
placeholder="Re-enter your password"
required="true"
type="password"
value={props.passwordConfirm} />
</div>
</div>
</form>
);
};
export default UserSignUpForm = reduxForm({
form: 'UserSignUpForm',
validate,
onSubmit: onHandleInfoSubmit
})(UserSignUpForm);
My form container
import React from 'react';
import UserSignUpForm from '../../components/UserSignUpForm';
import SignUpSubmitBtn from '../../components/SmallUIBits/SignUpSubmitBtn';
class SignUp extends React.Component {
render() {
return (
<div className="Middle col-lg-6 col-md-12 col-sm-12 col-xs-12">
<UserSignUpForm />
<SignUpSubmitBtn />
</div>
);
}
}
export default SignUp;
My redux-thunk action:
export const onHandleInfoSubmit = values => {
// trim data
const userInfo = Object.keys(values).reduce((previous, current) => {
previous[current] = values[current].trim();
return previous;
}, {});
const {
email,
password,
} = userInfo;
console.log(userInfo);
console.log('creating with email and password:');
console.log(email);
console.log(password);
//^^ Works fine. No problems submitting info.
//vv Does nothing. Return never returns.
return dispatch => {
// Auth imported from database.js
console.log('Creating new account);
auth.createUserWithEmailAndPassword(email, password)
.then(() => {
const {currentUser} = auth;
const userRef = database.ref(`users/${currentUser.uid}/data`);
userRef.set({
uid: currentUser.uid,
email: currentUser.email,
emailVerified: currentUser.emailVerified,
});
console.log('Account created successfully');
},
err => {
const errorCode = err.code;
const errorMessage = err.message;
if (errorCode || errorMessage) {
dispatch(newUserAccountCreateError(errorMessage));
console.log(errorCode + errorMessage);
}
});
};
};
Finally figured this out.
As it turns out, I didn't need to return a function or use dispatch to trigger any of the actions I wanted to fire after successful form submit.
So my thunk action should in fact look like this.
export const onHandleInfoSubmit = values => {
// trim data
const userInfo = Object.keys(values).reduce((previous, current) => {
previous[current] = values[current].trim();
return previous;
}, {});
const {
email,
password,
} = userInfo;
console.log(userInfo);
console.log('creating with email and password:');
console.log(email);
console.log(password);
//^^ No change needed
//vv remove the return function and all instances of dispatch()
// Auth imported from database.js
console.log('Creating new account);
auth.createUserWithEmailAndPassword(email, password)
.then(() => {
const {currentUser} = auth;
const userRef = database.ref(`users/${currentUser.uid}/data`);
userRef.set({
uid: currentUser.uid,
email: currentUser.email,
emailVerified: currentUser.emailVerified,
});
console.log('Account created successfully');
},
err => {
const errorCode = err.code;
const errorMessage = err.message;
if (errorCode || errorMessage) {
newUserAccountCreateError(errorMessage);
console.log(errorCode + errorMessage);
}
});
};
I still don't know if this is the solution because I'm using redux-forms in general or because I'm using the remote submit feature of redux-forms (and I'm not changing my code to find out), but I hope this helps anyone else having the same problem.