How to integrate redux with antd form validation - reactjs

I'm validating an email field with react-js, antd, and redux, my problem is why does the loading icon disappeared in the input when i integrated redux(created-form.js) but when i remove redux integration, the loading icon is working fine, am i missing something here, or doing something not right?
base-form.js
...
// Constructor
constructor() {
super();
this._validateEmail = _.debounce(this._validateEmail, 1000);
}
// Private method
_validateEmail = (rule, email, callback) => {
const url = 'http://localhost:8000/api/user/isExist';
axios
.post(url, { email })
.then(res => {
if (res.data.isExist) {
callback('Email is already exist');
}
callback();
})
.catch(res => console.log(res));
};
// Render
<Form.Item hasFeedback>
{getFieldDecorator('email', {
rules: [...rules.email, { validator: this._validateEmail }]
})(<Input placeholder="Email" />)}
</Form.Item>
...
created-form.js
import { Form } from 'antd';
import AccSetupForm from './base-form';
function mapPropsToFields(props) {
return {
email: Form.createFormField({
value: props.email
}),
password: Form.createFormField({
value: props.password
}),
confirm_pass: Form.createFormField({
value: props.confirm_pass
})
};
}
function onFieldsChange(props, changedField) {
const field = Object.values(changedField)[0];
if (field !== undefined) {
props.updateAccSetup({
[field.name]: field.value
});
}
}
const CreatedForm = Form.create({ mapPropsToFields, onFieldsChange })(
AccSetupForm
);
export default CreatedForm;
index.js
import { connect } from 'react-redux';
import { updateAccSetup } from '../actions';
import CreatedForm from './created-form';
function mapStateToProps(state) {
return {
email: state.getIn(['registration', 'user', 'email']),
password: state.getIn(['registration', 'user', 'password']),
confirm_pass: state.getIn(['registration', 'user', 'confirm_pass'])
};
}
function mapDispatchToProps(dispatch) {
return {
updateAccSetup: userInfo => dispatch(updateAccSetup(userInfo))
};
}
const StepOne = connect(
mapStateToProps,
mapDispatchToProps
)(CreatedForm);
export default StepOne;

I found the problem, i forgot to add ...props.username inside form.createFormField
/* Antd Docu */
mapPropsToFields(props) {
return {
username: Form.createFormField({
...props.username,
value: props.username.value,
}),
};
},
here are some reference:
https://github.com/ant-design/ant-design/issues/9561
https://ant.design/components/form/#components-form-demo-global-state

Related

Can I use Custom Hook inside class component

I have created one universal custom spinner/loader hook for my react application , I want to use that loader inside my component where I used to invoke API calls, the problem is the API calls are written inside the class components and as per the react doc we cannot use hooks inside the class component, most of the API calls written inside the class component.
I just want to know is there anyway to achieve the same, as the loader I have created is class based, but I then created a hook for the usage.
LoaderComponent
import React, {Component} from 'react';
import { Spin } from 'antd';
import 'antd/dist/antd.css';
import { LoadingOutlined } from '#ant-design/icons';
export default class LoaderComponent extends React.PureComponent {
render(){
const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
return (
<div className="fp-container">
<Spin indicator={antIcon}className="fp-loader" alt="loading" />;
</div>)
}
}
useLoader
import React from 'react'
import LoaderComponent from '../index'
export const useLoader = () => {
const[initLoad, setInitLoad]=useState(false)
return [ initLoad ? <LoaderComponent /> : null,
() => setInitLoad(true), //Show loader
() => setInitLoad(false) //Hide Loader
]
}
Component
The below is the component where I want to use the loader, there is two API calls implemneted into the same. I have tried to use the same but not suceeded.
import React, { Component } from 'react';
import { Row, Col , notification} from 'antd';
import WaitingForCallComponent from '#components/WaitingForCallComponent';
import { connect } from 'react-redux';
import { SET_AGENT_DETAILS } from '#actions';
import { SET_CONFIG_SERVER, GET_REGISTER_AGENT } from "#Utils/Urls";
import makeRequest from '#Utils/MakeRequest';
import { sessionService } from "redux-react-session";
import socketConnection from '#Hoc/SocketComponent';
import useLoader from '#Hoc/LoaderComponent/hook';
export class WaitingContainer extends Component {
constructor(props) {
super(props);
this.state = {
extensionNo: "",
agentId: "",
genesysId: "",
username: "",
agentStatus:"",
};
}
componentDidMount = () => {
window.history.pushState(null, document.title, window.location.href);
window.addEventListener('popstate', this.callWindow)
sessionService.loadUser().then((currentUser) => {
this.setState({
username: currentUser.name,
agentId: currentUser.params.AgentID,
genesysId: currentUser.params.genesysID,
}
, () => {
this.setConfig();
});
});
};
callWindow =()=>{
window.history.pushState(null, document.title, window.location.href);
}
handleException = (e) => {
notification.error({
message: 'Agent Registration Error',
description: e?.data?.description,
duration: 0
});
this.setState({
spinLoader: false
});
};
available = (extensionNo, agentId, genesysId) => {
makeRequest
.postAuth(GET_REGISTER_AGENT, {
data: {
extensionNo: extensionNo,
agentId: agentId,
reason: 'unknown',
agentStatus: 'ready',
genesysId: genesysId
}
})
.then((response) => {
if (response && response.data && !response.data.error) {
if (response.data.data.phoneStatus) {
this.props.setExtension({
agentStatus: response.data.data.agentStatus??'ready',
agentSessionId: response.data.data.agentSessionId,
extensionNo: extensionNo,
agentId: agentId,
genesysId: genesysId
});
this.setState({
agentStatus:response.data.data.agentStatus??'ready'
})
setTimeout(() => {
sessionService.loadUser().then((currentUser) => {
if (!currentUser.extraDetails) {
currentUser.extraDetails = {};
}
currentUser.extraDetails.agentStatus = response.data.data.agentStatus;
currentUser.extraDetails.agentSessionId = response.data.data.agentSessionId;
currentUser.extraDetails.extensionNo = extensionNo;
sessionService.saveUser(currentUser).then(() => {
socketConnection(this.props);
});
});
}, 1000);
} else {
this.handleException({
data: {
description: 'Please login into softphone extension ' + extensionNo
}
});
}
} else {
this.handleException(response);
}
})
.catch(this.handleException);
};
setConfig = () => {
sessionService.loadUser().then((currentUser) => {
makeRequest
.postAuth(SET_CONFIG_SERVER, {
data: {
username: currentUser?.params?.username,
},
})
.then((response) => {
if (response?.data?.data.extensionNo ?? false) {
this.setState({
extensionNo: response?.data?.data.extensionNo ?? "",
}, () => {
this.available(this.state.extensionNo, this.state.agentId, this.state.genesysId);
notification.success({
type: "success",
message: "Extension Number",
description: "Extension Verified",
});
})
} else {
notification.error({ type: "error", message: "Extension Number Error" });
}
})
.catch(function (event) {
console.error(event);
});
});
};
render() {
return (
<Row>
<Col span="24" className="lgnpges waitingPage">
<WaitingForCallComponent />
{loader}
</Col>
</Row>
);
}
}
export const mapStateToProps = (state) => {
return {
agentStatus: state?.agentDetails?.agentDetails?.agentStatus,
agentSessionId: state?.agentDetails?.agentDetails?.agentSessionId,
extensionNo: state?.agentDetails?.agentDetails?.extensionNo,
agentId: state?.agentDetails?.agentDetails?.agentId,
genesysId: state?.agentDetails?.agentDetails?.genesysId
};
};
export const mapDispatchToProps = (dispatch) => {
return {
setExtension: (value) => dispatch({ type: SET_AGENT_DETAILS, payLoad: value })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(WaitingContainer);
kindly suggest me the way or either is it required to create class based component for loader

How to configure or test a container with redux-mock-store in 2019?

I configured a container to test with redux-mock-store to the last version and I get some issues. The find() function not works. I ever receive zero nodes and zero length. When I use mount instead to shallow function this works but I get the issues where the redux mapDispatchToProps is not recognized. How I can guarantee that action will be called? I don't wanna test the store but the action function because I use thunk. Is my reasoning right?
My container:
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import styles from './Auth.module.css'
import Input from '../../components/UI/Input/Input'
import Button from '../../components/UI/Button/Button'
import Logo from '../../components/UI/Logo/Logo'
import Spinner from '../../components/UI/Spinner/Spinner'
import { auth as authAction } from '../../store/actions/index'
import { checkValidity } from '../../shared/utility'
export const Auth = (props) => {
const [formIsValid, setFormIsValid] = useState(false)
const [authForm, setAuthForm] = useState({
email: {
elementType: 'input',
elementConfig: {
type: 'email',
placeholder: 'Enter your email'
},
value: '',
validation: {
required: true,
isEmail: true
},
valid: false,
touched: false
},
password: {
elementType: 'input',
elementConfig: {
type: 'password',
placeholder: 'Enter your password'
},
value: '',
validation: {
required: true,
minLength: 6
},
valid: false,
touched: false
},
})
const inputChangeHandler = (event, controlName) => {
const updatedControls = {
...authForm,
[controlName]: {
...authForm[controlName],
value: event.target.value,
valid: checkValidity(event.target.value, authForm[controlName].validation),
touched: true
}
}
let formIsValid = true;
for (let inputIdentifier in updatedControls) {
formIsValid = updatedControls[inputIdentifier].valid && formIsValid
}
setAuthForm(updatedControls)
setFormIsValid(formIsValid)
}
const submitHandler = (event, signup) => {
event.preventDefault()
props.onAuth(
authForm.email.value,
authForm.password.value,
signup
)
}
const formElementsArray = []
for (let key in authForm) {
formElementsArray.push({
id: key,
config: authForm[key]
})
}
let formFields = formElementsArray.map(formElement => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={(event) => inputChangeHandler(event, formElement.id)} />
))
let form = (
<>
<form onSubmit={(event) => submitHandler(event, false)}>
{formFields}
<Button
disabled={!formIsValid}
btnType="Default">Log In</Button>
</form>
<Button
clicked={(event) => submitHandler(event, true)}
disabled={!formIsValid}
btnType="Link">Sign Up</Button>
</>
)
if (props.loading) {
form = <Spinner />
}
const errorMessage = props.error ? (
<div>
<p style={{ color: "red" }}>{props.error}</p>
</div>
) : null;
let authRedirect = null;
if (props.isAuthenticated) {
authRedirect = <Redirect to={'/'} />
}
return (
<main className={styles.Auth}>
{authRedirect}
<div className={styles.AuthForm}>
<h1>Log in to your account</h1>
<Logo height="3em" />
{errorMessage}
{form}
</div>
</main>
)
}
const mapStateToProps = (state) => {
return {
loading: state.auth.loading,
error: state.auth.error,
isAuthenticated: state.auth.token !== null,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onAuth: (email, password, isSignup) => dispatch(authAction(email, password, isSignup))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Auth)
My test:
import React from 'react';
import { Redirect } from 'react-router-dom';
import thunk from 'redux-thunk';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureStore from 'redux-mock-store';
import Auth from './Auth';
import Spinner from '../../components/UI/Spinner/Spinner';
import Button from '../../components/UI/Button/Button';
import Input from '../../components/UI/Input/Input';
configure({ adapter: new Adapter() });
const setup = () => {
const props = {
onAuth: jest.fn()
}
const middlewares = [thunk]
const mockStore = configureStore(middlewares);
const initialState = {
auth: {
token: null,
email: null,
error: null,
loading: false
}
};
const store = mockStore(initialState);
const enzymeWrapper = shallow(<Auth store={store} {...props} />).dive();
return {
enzymeWrapper,
props,
store
}
}
describe('<Auth />', () => {
it('should calls onSubmit prop function when form is submitted', () => {
const { enzymeWrapper: wrapper, props: reduxProps, store } = setup();
const form = wrapper.find('form');
form.simulate('submit', {
preventDefault: () => { }
});
expect(wrapper.props().onAuth).toHaveBeenCalled();
});
});
To be able to test the Auth class without the connection to store, you need to use the named import and not the default import. PFB the line to add in your test file for importing the Auth component:
import { Auth } from './Auth'; // notice the curly braces around the component name
Also, with this approach, you need not pass store as props to the component while rendering, and you can pass the actions as mocked functions (which you are already doing for onAuth action). Also you can use shallow with this approach.

Change in Redux state not reflected in component

I am building a prototype that displays a login form. The submit event triggers a lookup from a database. If the lookup fails, I wish to change the form to a) display the error message and b) discard the previous entry for user ID and password.
My reducer changes the state in Redux, but I am not sure how to transfer the data back to the component state.
Here is my form:
import React from 'react';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
export class LoginForm extends React.Component {
constructor(props) {
super(props);
console.log("Login form props", props);
this.state = {
userName: props.user ? props.user.userName : '',
password: props.user ? props.user.password : '',
error: props.error ? props.error : ''
}
}
onUserNameChange = (event) => {
const userName = event.target.value;
this.setState(() => ({ userName }));
};
onPasswordChange = (event) => {
const password = event.target.value;
this.setState(() => ({ password }));
};
onSubmit = (event) => {
event.preventDefault();
if (!this.state.userName || !this.state.password) {
this.setState(() => ({ error: 'User name and password are required.'}));
} else {
this.setState(() => ({ error: '' }));
this.props.onSubmit({
userName: this.state.userName,
password: this.state.password
})
}
};
render() {
console.log("Login form render() this.state", this.state);
// console.log("Login form render() this.props", this.props);
return (
<div>
{this.props.error && <p>{this.props.error}</p>}
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="User name"
autoFocus
value={this.state.userName}
onChange={this.onUserNameChange}
/>
<input
type="password"
placeholder="Password"
value={this.state.password}
onChange={this.onPasswordChange}
/>
<button>Sign In</button>
</form>
<NavLink to="/passwordRecovery" activeClassName="is-active" exact={true}>Forgot Password?</NavLink>
<NavLink to="/newUser" activeClassName="is-active">New User?</NavLink>
</div>
)
}
}
const mapStateToProps = (state) => {
console.log('in LoginForm state.authentication: ', state.authentication);
if (state.authentication.user)
{
return {
error: state.authentication.error,
userName: state.authentication.user.userName,
password: state.authentication.user.password
}
} else {
return {
error: state.authentication.error,
user: state.authentication.user
}
}
}
export default connect(mapStateToProps, undefined)(LoginForm);
Here is the page which displays the form:
import React from 'react';
import { connect } from 'react-redux';
import LoginForm from './LoginForm';
import { login, resetForm } from '../actions/authentication';
export class LoginPage extends React.Component {
onSubmit = (user) => {
console.log('LoginPage onSubmit user: ', user);
console.log('props ', this.props);
this.props.login(user);
if (this.props.user) {
this.props.history.push("/userHome");
}
}
render() {
console.log("LoginPage.render()", this.props)
return (
<div>
<LoginForm
onSubmit={this.onSubmit} error={this.props.error}
/>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
login: (user) => dispatch(login(user)),
resetForm: () => dispatch(resetForm())
});
const mapStateToProps = (state) => {
console.log('state.authentication: ', state.authentication);
return {
error: state.authentication.error,
user: state.authentication.user
};
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
Here is the reducer:
// reducer for authentication actions
const authenticationReducerDefaultState = {
userName: '',
password: ''
};
export default (state = authenticationReducerDefaultState, action) => {
console.log('in reducer, state: ', state);
console.log('in reducer, action: ', action);
switch (action.type) {
case 'LOGIN_REQUEST':
return {
user: action.user,
error: '',
loggedIn: false,
loggingIn: true
};
case 'LOGIN_SUCCESS':
return {
user: action.user,
error: '',
loggedIn: true,
loggingIn: false
}
case 'LOGIN_FAILURE':
return {
user: authenticationReducerDefaultState,
error: action.error,
loggedIn: false,
loggingIn: false
}
case 'LOGOUT':
return {
user: authenticationReducerDefaultState,
error: '',
loggedIn: false,
loggingIn: false
};
default:
return state;
};
};
Here is the action:
import database from '../firebase/firebase';
const request = (user) => ({
type: 'LOGIN_REQUEST',
user
});
const success = (user) => ({
type: 'LOGIN_SUCCESS',
user
});
const failure = (error) => {
// console.log('failure with error ', error);
return {
type: 'LOGIN_FAILURE',
user: { userName: '', password: '' },
error
}};
export const login = (user) => {
return (dispatch) => {
const { userName, password } = user;
// console.log(`login function for ${userName} password ${password}`);
dispatch(request(user));
let matchedUser = undefined;
return database.ref(`users`).once('value').then((snapshot) => {
snapshot.forEach((childSnapshot) => {
const user = childSnapshot.val();
if (user.userName === userName &&
user.password === password) {
matchedUser = user;
};
});
return matchedUser;
}).then((matchedUser) => {
console.log('matched user', matchedUser);
if (matchedUser) {
dispatch(success(user));
} else {
// console.log('dispatching failure');
dispatch(failure(`An error occurred looking up user ID ${userName}`));
};
console.log('end of login function');
});
}
}
// action generator for logout action
export const logout = () => ({
type: 'LOGOUT'
});
Here is my root reducer:
export default () => {
// Store creation
const store = createStore(
combineReducers({
authentication: authenticationReducer
}),
composeEnhancers(applyMiddleware(thunk))
);
return store;
}
I'm hoping someone has already been down this road. Thanks in advance.
The problem is that even though the props change (redux store is updated), you are using the local state inside LoginForm. You map the values to props only once (LoginForm.constructor).
If you want to react to redux store changes, you need to write some code in order to update the local state if something changes on the store.
static getDerivedStateFromProps (props) {
return {
userName: props.user ? props.user.userName : '',
password: props.user ? props.user.password : '',
error: props.error ? props.error : ''
}
}
Whatever you return in this method will end up updating the local component state.
This kind of scenarios is kind of difficult to maintain. You are mixing the concept of controlled and uncontrolled components. You are getting the initial values from props, mapping those to the local state, then handle the state changes locally (when the input changes) but also reacting to changes on the store.
Tip: If you use default props you don't have to check if this.props.user is available.
static defaultProps = {
user: {
userName: '',
password: '''
},
error: ''
}
Have you tried dropping the logic from your mapStateToProps
if (state.authentication.user)
{
return {
error: state.authentication.error,
userName: state.authentication.user.userName,
password: state.authentication.user.password
}
} else {
return {
error: state.authentication.error,
user: state.authentication.user
}
}
}
to:
return {
error: state.authentication.error,
userName: state.authentication.user.userName,
user: state.authentication.user
password: state.authentication.user.password
}

TypeError: _this.props.onCreate is not a function

Help me out, I am new to React and Javascript
Getting this error:"TypeError: _this.props.onCreate is not a function" although the function has been passed in the props and has been bound.
Here is my current code in react.
UserCreate.js
import React, { Component } from 'react';
class UserCreate extends Component {
constructor(props){
super(props);
this.state = {
email: ''
};
}
handleChange = email => event => {
this.setState(
{
[email]: event.target.value,
}
)
}
handleCreate = () => {
console.log('create', this.state.email);
this.props.onCreate({'email': this.state.email});
}
render() {
let userData = this.props.user && this.props.user.email;
return (
<div>
<h3> New User Form </h3>
<input onChange={this.handleChange('email')} placeholder="Email"/>
<button onClick={this.handleCreate}>Create</button>
</div>
);
}
}
export default UserCreate;
App.js
const USerCreateWithData = compose(
graphql(UserCreateMutation, {
props: (props) => ({
onCreate: (user) => {
props.mutate({
variables: { ...user },
optimisticResponse: () => ({ createUser: { ...user, __typename: 'User'}})
})
}
}
),
options: {
update: (dataProxy, { data: { createUser }}) => {
}
}
})
)(UserCreate);
UserCreateMutation
export default gql`
mutation UserCreateMutation($email: String!){
createUser(
email: $email
) {
__typename
id
email
}
}
`;
What I am doing wrong in here? I have tried every solutions that I have seen on google, stackoverflow but haven't found a solution yet.

How to pass my error message from server called by axios to my component

I'm really new to React. I have an axios request in my actions I want my error message to pass on the component I have this code :
import axios from 'axios';
import setAuthorizationToken from '../utils/setAuthorizationToken';
import jwtDecode from 'jwt-decode';
import { SET_CURRENT_USER, BASE_URL } from './types';
const instance = axios.create({
baseURL: BASE_URL
});
export function setCurrentUser(user) {
return {
type: SET_CURRENT_USER,
user
};
}
export function logout() {
return dispatch => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
setAuthorizationToken(false);
dispatch(setCurrentUser({}));
}
}
export function login(data) {
return dispatch => {
return instance.post('/authenticate', data).then(function(response) {
const token = response.data.accessToken;
const refreshToken = response.data.refreshToken;
localStorage.setItem('accessToken', token);
localStorage.setItem('refreshToken', refreshToken);
setAuthorizationToken(token);
dispatch(setCurrentUser(jwtDecode(token)));
})
.catch(function(error){
console.log('error: ', error.response.data);
});
}
}
Here is my Component:
import React from 'react';
import TextFieldGroup from '../common/TextFieldGroup';
import validateInput from '../../server/validations/login';
import { connect } from 'react-redux';
import { login } from '../../actions/authActions';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
errors: {},
isLoading: false
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
isValid() {
const { errors, isValid } = validateInput(this.state);
if (!isValid) {
this.setState({ errors });
}
return isValid;
}
onSubmit(e) {
e.preventDefault();
if (this.isValid()) {
this.setState({ errors: {}, isLoading: true });
this.props.login(this.state).then(
(res) => this.context.router.push('/'),
(error) => this.setState({ errors: error.response.data , isLoading: false }),
);
}
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
const { errors, username, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<h1>Login</h1>
{ errors.message && <div className="alert alert-danger">{errors.message}</div> }
<TextFieldGroup
field="username"
label="Username"
value={username}
error={errors.username}
onChange={this.onChange}
/>
<TextFieldGroup
field="password"
label="Password"
value={password}
error={errors.password}
onChange={this.onChange}
type="password"
/>
<div className="form-group"><button className="btn btn-primary btn-lg" disabled={isLoading}>Login</button></div>
</form>
);
}
}
LoginForm.propTypes = {
login: React.PropTypes.func.isRequired
}
LoginForm.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default connect(null, { login })(LoginForm);
Here is the console.log
error: Object {code: "UNAUTHORIZED", message: "Invalid username or password."}
Currently I don't know to pass my error message to component. I'm really new to React and Redux
First you have to add the initial state on reducer. For example
authReducer.js
const initialState = {
... // another state
errors: {}
}
function yourReducer(state = initialState, action) {
case 'SHOW_ERROR':
return {
...state,
errors: action.message
}
default:
return state
}
On login action dispatch the 'SHOW_ERROR'
authActions.js
export function login(data) {
return dispatch => {
return instance.post('/authenticate', data).then(function(response) {
...
// success
})
.catch(function(error){
// fail
dispatch({ type: 'SHOW_ERROR', message: error.response.data })
});
}
}
Then you need to map redux state to be a props on your component
LoginComponent.js
function mapStateToProps(state) {
return {
you: may.return.another.state.here,
errors: state.yourReducerName.errors
}
}
export default connect(mapStateToProps, { login })(LoginForm);
Finally, you can call errors as a props on your Component
class LoginForm extends React.Component {
...
render() {
const { errors, username, password, isLoading } = this.state;
const { errors } = this.props // errors from redux state
return (
<form onSubmit={this.onSubmit}>
<p>{errors.message}</p>
<h1>Login</h1>
...
<div className="form-group"><button className="btn btn-primary btn-lg" disabled={isLoading}>Login</button></div>
</form>
);
}
}
Don't forget to validate the prop types. Good luck!

Resources