react no-shadow rule handling - reactjs

I am pretty new in react. I am trying to create first app which should handle login. Actually everything works even now, but im getting eslint error with no-shadow on line 18.
This is my LoginForm controller:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button, TextInput, Logo, Block } from 'vcc-ui';
import { login } from '../../redux/reducer';
import { styles } from './LoginForm-styles';
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {};
}
onSubmit = (e) => {
e.preventDefault();
const { username, password } = this.state;
login(username, password);
}
render() {
const {username, password} = this.state;
const {isLoginPending, isLoginSuccess, loginError} = this.props;
return (
<Block
extend={styles.loginWrapper}
>
<Block
extend={styles.loginForm}
>
<Block
as="form"
name="loginForm"
onSubmit={this.onSubmit}
>
<Block
extend={styles.loginLogo}
>
<Logo height="60"/>
</Block>
<Block
extend={styles.loginInput}
>
<TextInput
value={username}
placeholder="username"
type="text" name="username"
onChange={e => this.setState({username: e.target.value})}
/>
</Block>
<Block
extend={styles.loginInput}
>
<TextInput
value={password}
placeholder="password"
type="password"
name="password"
onChange={e => this.setState({password: e.target.value})}
/>
</Block>
<Block
extend={styles.loginButton}
>
<Button
loading={isLoginPending}
variant="outline"
type="submit"
fullWidth={["s","m","l"]}
>
Login
</Button>
</Block>
{isLoginPending && <div>Processing</div>}
{isLoginSuccess && <div>Logged In</div>}
{loginError && <div>Incorrect Username or Password</div>}
</Block>
</Block>
</Block>
)
}
}
LoginForm.propTypes = {
isLoginPending: PropTypes.bool,
isLoginSuccess: PropTypes.bool,
loginError: PropTypes.string,
login: PropTypes.func
};
LoginForm.defaultProps = {
isLoginPending: false,
isLoginSuccess: false,
loginError: "",
login: () => undefined
};
const mapStateToProps = (state) => ({
isLoginPending: state.isLoginPending,
isLoginSuccess: state.isLoginSuccess,
loginError: state.loginError,
})
const mapDispatchToProps = (dispatch) => ({
login: (username, password) => dispatch(login(username,password))
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
onSubmit function is throwing no-shadow esling error. Please how can i rewrite it or define login prop so it will not throw it?
I know that login is somehow changing its value on 2 places, but i dont know how to make it more "nice" lets say.
Do anybody have some idea?
Thanks.

you can import your login with an alias:
import { login as reducerLogin } from '../../redux/reducer';
...
const mapDispatchToProps = (dispatch) => ({
login: (username, password) => dispatch(reducerLogin(username,password))
})

This error occurs when you have two variables with the same name but different scope.
By the way, I believe you should be using this.props.login(username, password); instead of calling the imported action creator directly.
You could give the mapped function whatever name you like. For instance:
// At line 18
this.props.loginAction(username, password)
// Mapping action creator shortcut...
export default connect(mapStateToProps,{ loginAction: login })(LoginForm)

Related

Unable to test React useState hook using Mocha and Enzyme

I have a login Component with two inputs one is for username and the other is for password. I have onChange function for both these inputs. I have written some test cases which works fine. I need to write few more test cases which involves testing of initial state and state after updating. I have researched a lot but couldn't find suitable example for useState hooks testing with MOCHA and Enzyme.
My last test case is failing.
Mocha is mandatory for me. Any help please?
Login Component
----------------
import React from 'react'
import { Form, FormGroup, TextInput, Button } from 'carbon-components-react'
import Login16 from '#carbon/icons-react/lib/login/16'
import { PropTypes } from 'prop-types'
function LoginComponent(props) {
const { username, password, onUsernameChange, onPasswordChange, onSubmit } = props;
return (
<div className="bx--row login-box">
<div className="bx--col-xs-6 bx--col-sm-6 bx--col-md-6 bx--col-lg-6 login-form">
<Form className="form-box">
<FormGroup legendText="Login">
<div>Sign in to your account</div>
<TextInput
className="login-input"
id="username"
name="username"
value={username}
onChange={onUsernameChange}
labelText=""
placeholder="User Name"
type="text"
/>
<TextInput
className="login-input"
id="password"
name="password"
value={password}
onChange={onPasswordChange}
labelText=""
placeholder="Password"
type="password"
/>
<Button
className=""
id="login-btn"
onClick={onSubmit}
>
<Login16 className="login-icon"/> Login
</Button>
</FormGroup>
</Form>
</div>
<div className="bx--col-xs-6 bx--col-sm-6 bx--col-md-6 bx--col-lg-6 login-image">
<img src="/ibmlogo.png" alt=""/>
</div>
</div>
)
}
LoginComponent.propTypes = {
username: PropTypes.string,
password: PropTypes.string,
onUsernameChange: PropTypes.func,
onPasswordChange: PropTypes.func,
onSubmit: PropTypes.func
}
export default LoginComponent
Login.test.js
-------------
import React from 'react'
import { shallow } from 'enzyme'
import chai, { expect } from 'chai';
import chaiEnzyme from 'chai-enzyme';
import { spy } from 'sinon'
import LoginComponent from '../components/Login';
describe('Login component testing', () => {
const handleChange = spy();
const props = {
username: "",
password: "",
onUsernameChange: handleChange,
onPasswordChange: handleChange,
onSubmit: () => {}
}
const wrapper = shallow(<LoginComponent {...props}/>);
let input;
beforeEach(() => {
input = ""
})
afterEach(() => {
})
it('Should have two inputs', () => {
input = wrapper.find('.login-input');
expect(input).to.have.length(2);
})
it('Should have one button to handle onSubmit', () => {
input = wrapper.find('#login-btn');
expect(input).to.have.length(1);
})
it('Should have an initial state for username and password to be empty string or undefined', () => {
expect(wrapper.find("#username").prop('value')).to.equal('');
expect(wrapper.find("#password").prop('value')).to.equal('');
})
it('Should have props for onUsernameChange, onPasswordChange, and onSubmit', () => {
expect(wrapper.find('#username').props().onChange).to.not.be.an('undefined');
expect(wrapper.find('#password').props().onChange).to.not.be.an('undefined');
expect(wrapper.find('#login-btn').props().onClick).to.not.be.an('undefined');
})
//below test case is failing.
it('Should update state for username and password onChange', () => {
const func = wrapper.find('#username');
func.simulate('change', { target: { value: "username" } })
console.log(wrapper.find('#username').debug())
expect(wrapper.find("#username").prop('value')).to.equal('username');
})
chai.use(chaiEnzyme());
})

eslint hoist never doesn't work in my react js app

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

How to clear input after form submission in react?

I want to clear input after my form submission get successful. I don't want to use reset button in this case.
I have passed submitted data to api that is in another file.
Please help.
file forgotPassword.js
import React, { Component } from "react";
import { Link, withRouter } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { forgotPassword } from "../../actions/authActions";
import classnames from "classnames";
class ForgotPassword extends Component {
constructor() {
super();
this.state = {
email:"",
errors: {}
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
onChange = e => {
this.setState({ [e.target.id]: e.target.value });
};
onSubmit = e => {
e.preventDefault();
var emailId = {
email: this.state.email
};
this.props.forgotPassword(emailId, this.props.history);
};
render(){
const { errors } = this.state;
return (
<div className="container">
<div className="row">
<div className="col s8 offset-s2">
<div className="col s12" style={{ paddingLeft: "11.250px" }}>
<h4><b>Forgot Password</b></h4>
</div>
<form noValidate onSubmit={this.onSubmit}>
<div className="input-field col s12">
<input
onChange={this.onChange}
value={this.state.email}
error={errors.email}
id="email"
type="email"
className={classnames("", {
invalid: errors.email
})}
/>
<label htmlFor="email">Email</label>
<span className="red-text">{errors.email}</span>
</div>
<div className="col s12" style={{ paddingLeft: "11.250px" }}>
<button
style={{
width: "150px",
borderRadius: "3px",
letterSpacing: "1.5px",
marginTop: "1rem"
}}
type="submit"
className="btn btn-large waves-effect waves-light hoverable blue accent-3"
>
Submit
</button>
</div>
</form>
</div>
</div>
</div>
);
}
onHandleSubmit(e) {
e.preventDefault();
const email = this.state.email;
this.props.onSearchTermChange(email);
console.log(email);
this.setState({
email: ''
});
}
}
ForgotPassword.propTypes = {
forgotPassword: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors
});
export default connect(
mapStateToProps,
{ forgotPassword }
)(ForgotPassword);
File authaction.js where calling api
import axios from "axios";
import setAuthToken from "../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING} from "./types";
export const forgotPassword = (userData, history) => dispatch => {
axios
.post("/api/users/forgotpassword", userData)
.then(res =>
console.log("forgot password",res)
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
After successfull result on api I'm unable to clear input in forgot password form component.
Please let me know if any other way to do this task.I'm a newbie in react.
Many thanks for help.
Changing the value of a TextInput component
You can change the context of the input after an onPress event coming from a button.
export default class App extends React.Component {
state = {
text : "Username"
}
render() {
return (
<View style={styles.container}>
// TextInput gets its value from the state.text above.
<TextInput value={this.state.text } style={{borderColor:"black", border:1}}/>
// Button calls the function in onPress when it is pressed, which cleans up the state.text
<Button title="CLEAN" onPress={() => this.setState({text: ""})} />
</View>
);
}
}

Formik form submission with react-testing library

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.

undefined is not a function 'onLogin' when use redux

I use connect to bind the actions 'isLoading' & 'onLogin', but it is undefined as 'this.props' is undefined in the log. I have checked many times that I think the code should be fine, but I cannot figure out what i missed in concept of redux.
And the next question 'is it a right way to pass params to the container in LoginButton.js?'
It is appreciated that someone may suggest some advice to help me, Thanks!!
I think some possible files that may cause errors in the followings.
My SourceCode
LoginButton.js
import {loginStyles} from 'App/styles';
import {loginUser} from 'App/redux/actions/actionCreators'
export const LoginButton = ({isLoading, onLogin, username, password }) => {
let loginButton;
console.log('props: ' + this.props);
if (isLoading){
loginButton = (
<Button primary full rounded >
<Spinner style={loginStyles.spinner} color='white'/>
<Text style={loginStyles.IconDivider}>Loading...</Text>
</Button>
)
}else{
loginButton = (
<Button primary full rounded
onPress={() => onLogin(username, password)}>
<Text>Login</Text>
</Button>
)
}
return loginButton;
}
const styles = StyleSheet.create({
buttonContainer:{
flex:1,
flexDirection:'row'
}
})
const mapStateToProps = state => ({
isLoading: state.auth.isLoading,
username: state.auth.username,
password: state.auth.password
});
const mapDispatchToProps = dispatch => ({
onLogin: (username, password) => dispatch(loginUser(username, password))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(LoginButton);
Login.js
import {LoginButton} from 'App/components';
export class Login extends Component {
constructor(props){
super(props);
this.state = {
username: '',
password: ''
}
}
render() {
return (
<Container>
<Content contentContainerStyle={styles.contentContainer}>
<Image source={images.login} style={styles.bg}>
<View style= {loginStyles.formContainer}>
<InputGroup style={loginStyles.input}>
<Icon name="ios-mail-outline" style={loginStyles.icon} />
<Input
onChangeText={(username) => this.setState({username})}
value={this.state.username}
placeholder={"Email Address"} />
</InputGroup>
<InputGroup style={loginStyles.input}>
<Icon name="ios-lock-outline" style={loginStyles.icon} />
<Input
onChangeText={(password) => this.setState({password})}
value={this.state.password}
secureTextEntry={true}
placeholder={"Password"} />
</InputGroup>
<View style={loginStyles.button}>
<LoginButton username={this.state.username} password={this.state.password}/>
</View>
</View>
</Image>
</Content>
</Container>
);
}
}
const mapStateToProps = state => {
return {
payload: state.auth.payload
};
}
export default connect(mapStateToProps, null)(Login);
actionCreators.js
function loginSuccess(username, password) {
return {
type: types.LOGIN_SUCCESS,
username: username,
password: password
}
}
function loginFailure(error) {
return {
type: types.LOGIN_FAILURE,
payload: error
}
}
export function loginUser(username, password) {
return function(dispatch) {
if (username == 'admin' && password == '123qwe'){
dispatch(loginSuccess(username, password));
}else{
dispatch(loginFailure('Incorrect username or password'));
}
};
}
There are 2 things that I've noticed that may cause problems in your code:
You are creating a stateless component but you are trying to log
this.props which won't work as you are not inside a class
component and you are not expecting any props parameters in your
function signature:
export const LoginButton = ({isLoading, onLogin, username, password }) => {...
In LoginButton.js you are exporting a named object LoginButton
in addition to the default export which is the component that
connects to redux:
export default connect(
mapStateToProps,
mapDispatchToProps
)(LoginButton);
But in Login.js you are importing the named export (which isn't
connected to redux):
import {LoginButton} from 'App/components';
You should change that to a default import syntax:
import LoginButton from 'App/components';

Resources