I got an issue may cause from react-boilerplate Or "redux-form/immutable", wish someone can help me out.
I tried to put some custom props into Form component and this will print out messages of error when submit.
Here is my code:
import React from 'react';
import { Form, Icon } from 'semantic-ui-react';
import { PropTypes } from 'prop-types';
import { Field, reduxForm, reset } from 'redux-form/immutable';
import { connect } from 'react-redux';
import { ReduxFormInput, ReduxFormCheckbox } from '../../components/ReduxFormInput';
import { signupSync, passStrength } from '../../components/Validate';
import StyledButton from '../../components/StyledButton';
import AcceptTerms from './acceptTerms';
import signupRequest from './actions';
class Signup extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
signupRequest: PropTypes.func,
signup: PropTypes.shape({
requesting: PropTypes.bool,
successful: PropTypes.bool,
messages: PropTypes.array,
errors: PropTypes.array,
}),
}
submit(values, dispatch) {
console.log(values);
this.props.signupRequest(values); // will be undefined 'props' after submit
}
render() {
const {
handleSubmit,
submitting,
signup: {
requesting,
successful,
messages,
errors,
},
} = this.props;
return (
<Form onSubmit={handleSubmit(this.submit)} >
<Form.Field>
<Field
type="text"
name="accountName"
component={ReduxFormInput}
icon="user outline"
label="Tên tài khoản"
placeholder="Tên tài khoản của bạn"
/>
</Form.Field>
<Form.Field >
<Field
type="email"
name="email"
component={ReduxFormInput}
icon="mail outline"
label="Email"
placeholder="Email của bạn"
/>
</Form.Field>
<Form.Field required >
<Field
type="password"
name="password"
component={ReduxFormInput}
icon="lock"
label="Mật khẩu"
placeholder="Nhập mật khẩu"
warn={passStrength}
/>
</Form.Field>
<Form.Field required >
<Field
type="password"
name="confirmPassword"
component={ReduxFormInput}
icon="lock"
label="Xác nhận Mật khẩu"
placeholder="Xác nhận lại mật khẩu"
/>
</Form.Field>
<Form.Field>
<Field
defaultChecked
type="checkbox"
name="confirm"
checkboxLabel="Tôi muốn nhận thông tin thông qua email, SMS, hoặc điện thoại."
component={ReduxFormCheckbox}
/>
</Form.Field>
{AcceptTerms}
<div>
<StyledButton primary fluid type="submit" disabled={submitting} >
<Icon name="add user" />
Đăng ký tài khoản
</StyledButton>
</div>
</Form>
);
}
}
const mapStateToProps = (state) => ({
signup: state.signup,
});
const connected = connect(mapStateToProps, { signupRequest })(Signup);
const formed = reduxForm({
form: 'signup',
validate: signupSync,
onSubmitSuccess: afterSubmit,
})(connected);
const afterSubmit = (result, dispatch) => dispatch(reset('signup'));
export default formed;
My reducer
import { SubmissionError } from 'redux-form/immutable';
import {
SIGNUP_REQUESTING,
SIGNUP_SUCCESS,
SIGNUP_ERROR,
} from './constants';
const initialState = {
requesting: false,
successful: false,
errors: [],
messages: [],
};
const reducer = function signupReducer(state = initialState, action) {
switch (action.type) {
case SIGNUP_REQUESTING:
return {
requesting: true,
successful: false,
errors: [],
messages: [{
body: 'Signing up...',
time: new Date(),
}],
};
case SIGNUP_SUCCESS:
return {
requesting: false,
successful: true,
errors: [],
messages: [{
body: `Successfully created account for ${action.response.email}`,
time: new Date(),
}],
};
case SIGNUP_ERROR:
return {
requesting: false,
successful: false,
messages: [],
errors: new SubmissionError({
email: 'failed',
_error: 'failed',
}),
};
default:
return state;
}
};
export default reducer;
Inject reducer on routes.js
...
{
path: '/signup',
name: 'signup',
getComponent(nextState, cb) {
const importModules = Promise.all([
import('containers/SignupPage/reducer'),
import('containers/SignupPage/sagas'),
import('containers/SignupPage'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, sagas, component]) => {
injectReducer('signup', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
},
}
...
Then I got an error screen like this.
TypeError: Cannot read property 'requesting' of undefined
Signup.render
/Users/son/Desktop/we-mak/app/containers/SignupPage/signupForm.js
Signup.tryRender
http://localhost:3000/main.js:1388:1
Signup.proxiedMethod
http://localhost:3000/main.js:1356:1
eval
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:796:21
measureLifeCyclePerf
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:75:12
ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:795:25
ReactCompositeComponentWrapper._renderValidatedComponent
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:822:32
ReactCompositeComponentWrapper._updateRenderedComponent
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:746:36
ReactCompositeComponentWrapper._performComponentUpdate
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:724:10
ReactCompositeComponentWrapper.updateComponent
webpack:///./~/react-dom/lib/ReactCompositeComponent.js?:645:12
I notice that react-boilerplate doesn't use react-hot-loader so I guessed it may cause from boilerplate, but I don't have enough webpack experience to config it.
This error message means your signup property is undefined, which may happen if your state does not have signup property or that property is undefined. Have a look at your reducer.
Related
Goal:
Retrieve data 'smartphones', by API, and apply it as a default selection in the input radio component for the react hook form.
Problem:
The code do not work that makes smartphones as a preselection in the input radio button.
However, if I make a change by using hard coding, it works but hard coding do not solve the case.
I don't know how to solve it.
Info:
*Using React TS and React hook form.
*Newbie in react TS and hook form.
Stackblitz:
https://stackblitz.com/edit/react-ts-z9cnzl
Thank you!
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import './style.css';
type FormValues = {
lastName: string;
favShow: string;
};
export default function App() {
const [category, setCategory] = useState('');
React.useEffect(() => {
async function FetchData() {
var data = await fetch('https://dummyjson.com/products/1').then((res) => {
return res.json();
});
console.log(data.category);
setCategory(data.category);
}
FetchData();
}, []);
const [data, setData] = useState(null);
const { register, handleSubmit } = useForm<FormValues>({
defaultValues: {
lastName: 'asaaaaaaf',
favShow: category,
//favShow: 'smartphones',
},
});
const onSubmit = (data) => {
setData(data);
console.log('asdf');
};
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)} className="form-container">
<h1 className="form-heading">React Hook Form Example</h1>
<input
{...register('lastName', { required: true })}
className="form-control"
type="text"
placeholder="Last name"
maxLength={15}
name="lastName"
/>
<br />
<br />
<label htmlFor="ted-lasso">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="smartphones"
id="smartphones"
/>{' '}
smartphones
</label>
<label htmlFor="got">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="GOT"
id="got"
/>
GOT
</label>
<br />
<br />
<button className="submit-btn" type="submit">
Submit
</button>
</form>
{data && <p className="submit-result">{JSON.stringify(data)}</p>}
</React.Fragment>
);
}
I got some help and the solution is:
Stackblitz:
https://stackblitz.com/edit/react-ts-m2s6ev?file=index.tsx
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import './style.css';
type FormValues = {
lastName: string;
favShow: string;
};
export default function App() {
const [category2, setCategory2] = useState();
const [data, setData] = useState(null);
const { register, handleSubmit, resetField } = useForm<FormValues>({
defaultValues: {
lastName: '',
favShow: '',
},
});
React.useEffect(() => {
async function FetchData() {
var data = await fetch('https://dummyjson.com/products/1').then((res) => {
return res.json();
});
setCategory2(data);
}
FetchData();
}, []);
React.useEffect(() => {
if (category2) {
const obj = JSON.parse(JSON.stringify(category2));
console.log(obj);
resetField('lastName', { defaultValue: obj.discountPercentage });
resetField('favShow', { defaultValue: obj.category });
}
}, [resetField, category2]);
const onSubmit = (data) => {
setData(data);
};
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)} className="form-container">
<h1 className="form-heading">React Hook Form Example</h1>
<input
{...register('lastName', { required: true })}
className="form-control"
type="text"
placeholder="Last name"
maxLength={15}
name="lastName"
/>
<br />
<br />
<label htmlFor="ted-lasso">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="smartphones"
id="smartphones"
/>{' '}
smartphones
</label>
<label htmlFor="got">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="GOT"
id="got"
/>
GOT
</label>
<br />
<br />
<button className="submit-btn" type="submit">
Submit
</button>
</form>
{data && <p className="submit-result">{JSON.stringify(data)}</p>}
</React.Fragment>
);
}
My brain broke. Why server send this error.I looked through all the articles with the same error and did not find a solution. What is the problem? How i can fixed this error? Please help.
My code.
App.jsx
This is client for Apollo Client
const client = new ApolloClient({
uri: "http://localhost:4000/api/",
request: (operation) => {
const token = localStorage.getItem("token");
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ""
}
});
}
});
signUp.js
This is mutation for sighUp
import { gql } from "apollo-boost";
export default gql`
mutation signup($firstName: String!, $secondName: String!, $email: String!, $password: String! ) {
signup(firstName: $firstName, secondName: $secondName, email: $email, password: $password )
}
`;
RegForm.jsx
It is my component registration
import React, {Component} from 'react'
import {Field, reduxForm, SubmissionError} from 'redux-form'
import regForm from './RegForm.module.css'
import { matchInput, passLength, email, required} from '../../../utils/validators'
import RegMutation from '../../../queries/signup'
import Button from '../../../UI/Button/Button'
import myInput from '../../../UI/Input/Input'
import { withMutation } from "react-apollo";
const passwordValidator = passLength(8);
class RegForm extends Component {
constructor(props){
super(props)
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(fields) {
console.log('Fields from handleSubmit',fields);
const { mutate } = this.props;
return new Promise((resolve, reject) => {
mutate({
variables: {
firstName: fields.loginField,
email: fields.email,
password: fields.passwordField
}
})
.then(res => {
console.log(res.data);
resolve(res);
})
.catch(e => {
reject(new SubmissionError({ _error: e?.message }));
});
});
}
render(){
return (
<div>
<form
className={regForm.formContent}
onSubmit={this.props.handleSubmit(this.handleSubmit)}
>
<Field
name='loginField'
type='text'
component={myInput}
validate={[required]}
/>
<Field
name='email'
type='text'
component={myInput}
validate={[email]}
/>
<Field
name='passwordField'
type='password'
component={myInput}
validate={[passwordValidator]}
placeholderText='Введите пароль'
/>
<Field
name='repPas'
type='password'
component={myInput}
validate={[matchInput]}
/>
<Button
onClick={this.Click}
className={regForm.button}>Sign Up
</Button>
</form>
{this.props.error ? <span>{this.props.error}</span> : null}
</div>
)
}
}
const connectedToReduxForm = reduxForm({
form: "loginForm",
});
export default withMutation(RegMutation)(connectedToReduxForm(RegForm))
secondName field is absent. I've known about this one by running full the project)
screenshot
I am struggling to get the value out of the Material-ui Autocomplete when using redux-form. Has anyone solved this? I am using the exact example from the material-ui Autocomplete https://material-ui.com/components/autocomplete/ I am able to see the list options and it populates after clicking it twice, but I am unable to extract the real value, instead I am returning ({ title : 0 }) instead of the value.
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { Field, reduxForm } from "redux-form";
import { connect } from "react-redux";
class Form extends React.Component {
onSubmit = formValues => {
console.log(formValues);
};
renderTextField = ({
label,
input,
meta: { touched, invalid, error },
...custom
}) => (
<Autocomplete
label={label}
options={this.props.movies}
placeholder={label}
getOptionLabel={option => option.title}
onChange={input.onChange}
{...input}
{...custom}
renderInput={params => (
<TextField {...params} label={label} variant="outlined" fullWidth />
)}
/>
);
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit)}>
<Field
name="propertySelector"
component={this.renderTextField}
label="Select Property"
type="text"
/>
</form>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state);
return {
movies: [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "Schindler's List", year: 1993 }
]
};
};
Form = reduxForm({
form: "auto_complete"
});
export default connect(mapStateToProps, null)(Form);
Solved by passing in the (event, value) to the onChange props.
onChange={(event, value) => console.log(value)}
From the docs;
Callback fired when the value changes.
Signature:
function(event: object, value: T) => void
event: The event source of the callback.
value: null
Homepage.js
import React, { Component } from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
import $ from 'jquery';
import { css } from 'glamor';
import { ToastContainer } from 'react-toastify';
import toast from '../toast';
import { BarLoader } from 'react-spinners';
// ---------------- Custom components
import Header from '../Header/Header';
import Footer from '../Footer/Footer';
import RelayAnimation from '../RelayAnimation/RelayAnimation';
import UserLoginForm from '../UserLoginForm/UserLoginForm';
import UserSignUpForm from '../UserSignUpForm/UserSignUpForm';
import PassResetReqForm from '../PassResetReqForm/PassResetReqForm';
import PassResetForm from '../PassResetForm/PassResetForm';
import './HomePage.css';
// --------- Message for Network Error
const Msg = () => (
<div>
Error please, try again later <br /> or reload the Page.
</div>
);
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
this.toggleLoader = this.toggleLoader.bind(this);
this.notifySuccess = this.notifySuccess.bind(this);
this.notifyError = this.notifyError.bind(this);
}
notifySuccess(msg) {
toast.success(msg);
}
notifyError(msg) {
toast.error(msg);
}
// --------- Toast Notifications ---------------
// notify = (status) => {
// // --------- Server Issue Toaster
// if (status === 'Bad Gateway') {
// toast.error(<Msg />, {
// className: {
// color: '#fff',
// minHeight: '60px',
// borderRadius: '8px',
// boxShadow: '2px 2px 20px 2px rgba(0,0,0,0.3)'
// }
// });
// }
// }
toggleLoader() {
this.setState({
loading: !this.state.loading
});
}
isAuthenticated() {
const token = localStorage.getItem('authToken');
if (token) {
return true;
}
}
componentDidMount() {
const currentLocationPath = this.props.location.pathname;
const urlForEmailVerification = currentLocationPath.includes('/api/v1/verifyEmailUser/');
if (urlForEmailVerification) {
const { token } = this.props.match.params; // token value from url params passed by <Route/>
if (token) {
const url = `/api/v1/verifyEmailUser/${token}`;
// api call to make the user's account verified in db based on token in url
$.ajax({
url: url,
dataType: 'json',
type: 'GET',
success: function (res) {
console.log(res);
this.notifySuccess('emailVerified');
this.props.history.push('/');
}.bind(this),
error: function (xhr, status, err) {
console.error(url, status, err.toString());
}.bind(this)
});
}
}
}
render() {
const currentLocationPath = this.props.location.pathname;
const isAuthenticated = this.isAuthenticated();
const resetPasswordPathname = currentLocationPath.includes('/api/v1/resetPassword/');
if (!isAuthenticated) {
return (
<div className="App d-flex flex-column">
{/* Navbar with brand logo and language change dropdown and signup/login button */}
< Header />
{/* Main Section with RelayStream Animation graphic and forms */}
<div className="container py-4 py-md-0 pt-lg-4 d-flex flex-grow" >
<div className={'LoginScreen d-flex align-items-center align-items-lg-start ' +
((currentLocationPath === '/login' ||
currentLocationPath === '/signup' ||
currentLocationPath === '/forgot-password' ||
resetPasswordPathname) ? 'justify-content-around' : 'justify-content-center')}>
{/* RelayStream Animation graphic */}
<RelayAnimation />
{/* forms to switch between based on path change by <Router/> */}
<Route path="/login" component={(props) => <UserLoginForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
<Route path="/signup" component={(props) => <UserSignUpForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
<Route path="/forgot-password" component={(props) => <PassResetReqForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
<Route path="/api/v1/resetPassword/:token" component={(props) => <PassResetForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
</div>
</div >
{/* Footer with copyright message */}
<Footer />
<div className={this.state.loading ? 'loader flex-column' : 'd-none'}>
<span className="loader__title">Loading...</span>
<BarLoader color={'#36D7B7'} loading={this.state.loading} />
</div>
{/* React toastify for toast notification */}
<ToastContainer className={{ textAlign: 'center' }} progressClassName={css({ background: '#007aff' })} />
</div >
);
} else {
return <Redirect to={'/dashboard'} />;
}
}
}
export default withRouter(HomePage);
UserLoginForm.js
import React, { Component } from 'react';
import { Link, Redirect } from 'react-router-dom';
import $ from 'jquery';
import { Animated } from 'react-animated-css';
import SocialButton from '../SocialButton/SocialButton';
// ---------------- Form components
import Form from 'react-validation/build/form';
import Button from 'react-validation/build/button';
// ---------------- Custom Form components & validations
import { Email, Password, required, noSpace, minChar8, email } from '../formValidation';
import FontAwesomeIcon from '#fortawesome/react-fontawesome';
import facebook from '#fortawesome/fontawesome-free-brands/faFacebookF';
import google from '#fortawesome/fontawesome-free-brands/faGooglePlusG';
import './UserLoginForm.css';
class UserLoginForm extends Component {
constructor(props) {
super(props);
this.state = {
fireRedirect: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSocialLogin = (user) => {
const provider = user._provider;
const name = user._profile.name;
const email = user._profile.email;
const profilePic = user._profile.profilePicURL;
const token = user._token.accessToken;
const data = { provider, name, email, profilePic, token };
console.log(data);
const url = '/api/v1/loginWithFacekbook'; // social login's api url
// api call for social logins
$.ajax({
url: url,
dataType: 'json',
type: 'POST',
data: data,
success: function (res) {
console.log('success response after api call ===>>', res);
const generatingAuthToken = res.object.generatingAuthToken;
const apikey = generatingAuthToken.apiKey;
const authToken = generatingAuthToken.authToken;
localStorage.setItem('apiKey', apikey);
localStorage.setItem('authToken', authToken);
// if social login was successful then redirect user to dashboard
this.setState({ fireRedirect: true });
}.bind(this),
error: function (xhr, status, err) {
console.log(status);
// if there was network issue notify user to try again later or refresh page
this.props.notifyError(err.toString());
console.error(url, status, err.toString());
}.bind(this)
});
}
handleSocialLoginFailure = (err) => {
console.error(err)
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
// input field animation code - adds class to current focused input field's parent
if (value) {
target.parentElement.classList.add('input--filled');
} else {
target.parentElement.classList.remove('input--filled');
}
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
// show loading spinner
this.props.toggleLoader();
// get data from all field in form
const data = this.form.getValues();
const url = '/api/v1/loginUser'; // user login api url
// api call to generate token and apikey and login user to dashboard
$.ajax({
url: url,
dataType: 'json',
type: 'POST',
data: data,
success: function (res) {
console.log('success response after api call ===>>', res);
const obj = res.object;
const loginStatus = res.status;
const msg = res.message;
// check if authToken and apiKey was received
if (obj) {
// if data is found in database check if credentials provided were correct
if (loginStatus) {
JSON.stringify(obj);
// save apiKey and token in loacalStorage
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let val = obj[key];
localStorage.setItem(key, val);
}
}
// turn off loader spinner
this.props.toggleLoader();
this.setState({ fireRedirect: true });
// if credentials were correct accept login then redirect user to dashboard
} else { // if credentials were wrong
// turn off loader spinner
// this.props.toggleLoader();
// then notify about wrong credentials
this.props.notifyError(msg);
}
} else { // if data was not found in database notify user to signup first
this.props.notifyError(msg);
// turn off loader spinner
// this.props.toggleLoader();
}
}.bind(this),
error: function (xhr, status, err) {
console.log(status);
// if there was network issue notify user to try again later or refresh page
this.props.notify(err.toString());
console.error(url, status, err.toString());
}.bind(this)
});
}
render() {
const { fireRedirect } = this.state;
return (
<Animated className="form-animation" animationIn="fadeInRight" animationOut="fadeOutLeft" isVisible={true}>
<Form className="userLoginForm" ref={c => { this.form = c }} onSubmit={this.handleSubmit} noValidate>
<h2 className="formTitle text-center">LOG IN</h2>
<Email
required
pattern="[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,3}$"
id="email"
name="email"
type="email"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[required, email]} />
{/* must wrap Password Field Component with "div.form-group" */}
<div className="form-group">
<Password
required
id="password"
name="password"
type="password"
minLength="8"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[noSpace, required, minChar8]} />
{/* Optional Link Component below...
Note: If there is no requirement of Link or any Other Element just below <Password/> input field
in entire project then the wrapping "div.form-group" can also be put inside
Password Component's Template Located in formValidation.js file*/}
<Link to="/forgot-password" className="float-right forgotPassword">
<small>Forgot password ?</small>
</Link>
</div>
<div className="form-group submitGroup text-center">
<Button type="submit" className="btn btn--submit btn-rounded btn-outline-primary mx-auto">LOG IN</Button>
</div>
{/* login buttons for facebook and google login */}
<div className="socialLogin mx-auto">
{/* <a href="#" className="socialBtn socialBtn--facebook rounded-circle">
<FontAwesomeIcon icon={facebook} />
</a> */}
<SocialButton
className="socialBtn socialBtn--facebook rounded-circle"
provider='facebook' appId='873380466175223'
onLoginSuccess={this.handleSocialLogin}
onLoginFailure={this.handleSocialLoginFailure}
redirect="/dashboard">
<FontAwesomeIcon icon={facebook} />
</SocialButton>
<span className="seperator" />
<SocialButton
className="socialBtn socialBtn--googlePlus rounded-circle"
provider="google"
appId="843586925977-d7j31p5j0me5kqvcp29nr9s37reg5b5u.apps.googleusercontent.com"
onLoginSuccess={this.handleSocialLogin}
onLoginFailure={this.handleSocialLoginFailure}>
<FontAwesomeIcon icon={google} />
</SocialButton>
{/* <a href="#" className="socialBtn socialBtn--googlePlus rounded-circle">
<FontAwesomeIcon icon={google} />
</a> */}
</div>
{/* code to redirect user to dashboard page after successful login */}
{fireRedirect && (<Redirect to={'/dashboard'} />)}
</Form>
</Animated>
);
}
}
export default UserLoginForm;
UserSignUpForm.js
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import $ from 'jquery';
import { Animated } from 'react-animated-css';
// ---------------- Form components
import Form from 'react-validation/build/form';
import Button from 'react-validation/build/button';
// ---------------- Custom Form components & validations
import { UserName, Email, NewPassword, ConfirmPassword, noSpace, required, minChar8, email, confirmPassword } from '../formValidation';
import './UserSignUp.css';
class UserSignUpForm extends Component {
constructor(props) {
super(props);
this.state = {
userName: '',
email: '',
password: '',
confirmPassword: '',
fireRedirect: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
// input field animation code - adds class to current focused input field's parent
if (value) {
target.parentElement.classList.add('input--filled');
} else {
target.parentElement.classList.remove('input--filled');
}
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
const data = this.form.getValues();
console.log(data);
// api call to sign up user
$.ajax({
url: '/api/v1/user',
dataType: 'json',
type: 'POST',
data: data,
success: function (res) {
console.log('success response after api call ===>>', res);
const signUpStatus = res.status;
const msg = res.message;
if (signUpStatus) {
// if signup was successful notify user
this.props.notifySuccess(msg);
// redirect user to login form
this.setState({ fireRedirect: true });
} else {
this.props.notifyError(msg); // notify user on signup fail
}
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}
render() {
const { fireRedirect } = this.state;
return (
<Animated className="form-animation" animationIn="fadeInRight" animationOut="fadeOutLeft" isVisible={true}>
<Form className="userSignUpForm" ref={c => { this.form = c }} onSubmit={this.handleSubmit}>
<h2 className="formTitle text-center">SIGN UP</h2>
<UserName
required
id="userName"
name="userName"
ref={c => { this.UserName = c }}
value={this.state.userName}
type="text"
className="input__field input__field--madoka"
validations={[required]}
onChange={this.handleInputChange} />
<Email
required
pattern="[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,3}$"
id="email"
name="email"
ref={c => { this.Email = c }}
value={this.state.email}
type="email"
className="input__field input__field--madoka"
validations={[required, email]}
onChange={this.handleInputChange} />
<div className="form-group">
<NewPassword
required
id="password"
name="password"
ref={c => { this.NewPassword = c }}
value={this.state.password}
type="password"
minLength="8"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[noSpace, required, minChar8]} />
</div>
<div className="form-group">
<ConfirmPassword
required
id="confirmPassword"
name="confirmPassword"
ref={c => { this.ConfirmPassword = c }}
value={this.state.confirmPassword}
type="password"
minLength="8"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[noSpace, required, confirmPassword]} />
</div>
<div className="form-group submitGroup text-center">
<Button type="submit" className="btn btn--submit btn-rounded btn-outline-primary mx-auto">SIGN UP</Button>
</div>
{/* code to redirect user to dashboard page after successful login */}
{fireRedirect && (<Redirect to={'/login'} />)}
</Form>
</Animated>
);
}
}
export default UserSignUpForm;
i am facing many issues. first i have a spinner in homepage.js which shows as overlay on whole page when this.state.loading = true, initially it is set to False in constructor.
Issues 1.) when we submit the login form (if signup was done already ) the loader shows for fraction of second on screen but " this.setState({ fireRedirect: true }); " this code is supposed to redirect user to dashboard which it does but gives error in console :
index.js:2178 Warning: Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the UserLoginForm component.
Issue 2.) If i create a new user from UserSignUpForm which redirects us to UserLoginForm. now if i try to login i get only this error:
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the UserLoginForm component.
Issue 3.) If i remove or comment "this.toggleLoader();" from the ajax's success functions right before " this.setState({ fireRedirect: true }); " this line. then the loader shows on screen but doesn't go away even page doesn't redirect and console gives this error:
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the UserLoginForm component.
How to enable the circular progress when user clicks on submit on the login page? I can able to see the loader symbol in the app bar on other pages but I'm not able to activate it on the login page.
We need to add Custom reducer for login page. I did it in the following way.
1.1. Create a new login page. Just copy and paste the admin-on-rest login page code.
1.2. Update the propTypes like below
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
1.3. Add the below line
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
1.4. Update the login page with below code.
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
1.5. Replace the submit button code
<CardActions>
<RaisedButton type="submit" primary disabled={isLogging} icon={isLogging && <CircularProgress size={25} thickness={2} />} label={translate('aor.auth.sign_in')} fullWidth />
</CardActions>
1.6 The complete code for the login page is
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Card, CardActions } from 'material-ui/Card';
import Avatar from 'material-ui/Avatar';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import CircularProgress from 'material-ui/CircularProgress';
import { cyan500, pinkA200, white } from 'material-ui/styles/colors';
import defaultTheme, {translate, Notification, userLogin as userLoginAction } from 'admin-on-rest';
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
},
card: {
minWidth: 300,
},
avatar: {
margin: '1em',
textAlign: 'center ',
},
avatarText:{
verticalAlign:'middle',
fontSize:20,
},
form: {
padding: '0 1em 1em 1em',
},
input: {
display: 'flex',
},
};
function getColorsFromTheme(theme) {
if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 };
const {
palette: {
primary1Color,
accent1Color,
},
} = theme;
return { primary1Color, accent1Color };
}
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) =>
<TextField
errorText={touched && error}
{...inputProps}
{...props}
fullWidth
/>;
class Login extends Component {
login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/');
render() {
const { handleSubmit, submitting, theme, translate, isLogging } = this.props;
const muiTheme = getMuiTheme(theme);
const { primary1Color } = getColorsFromTheme(muiTheme);
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div style={{ ...styles.main, backgroundColor: primary1Color }}>
<Card style={styles.card}>
<div style={styles.avatar}>
<div>
<Avatar backgroundColor={white} src="EnsembleGreenLogo.png" size={45} />
</div>
<div>
<span style={styles.avatarText}>Ensemble SmartWAN Manager</span>
</div>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div style={styles.form}>
<div style={styles.input} >
<Field
name="username"
component={renderInput}
floatingLabelText={translate('aor.auth.username')}
disabled={submitting}
/>
</div>
<div style={styles.input}>
<Field
name="password"
component={renderInput}
floatingLabelText={translate('aor.auth.password')}
type="password"
disabled={submitting}
/>
</div>
</div>
<CardActions>
<RaisedButton
type="submit"
primary
disabled={isLogging}
icon={isLogging && <CircularProgress size={25} thickness={2} />}
label={translate('aor.auth.sign_in')}
fullWidth
/>
</CardActions>
</form>
</Card>
<Notification />
</div>
</MuiThemeProvider>
);
}
}
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
Login.defaultProps = {
theme: defaultTheme,
};
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
2.1. Add a new file (src/loginReducer.js) in src folder with the below content
import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, USER_CHECK } from 'admin-on-rest';
export default (previousState = 0, { type }) => {
switch (type) {
case USER_LOGIN_LOADING:
return previousState + 1;
case USER_LOGIN_SUCCESS:
case USER_LOGIN_FAILURE:
case USER_CHECK:
return Math.max(previousState - 1, 0);
default:
return previousState;
}
};
3.1 Update the app.js admin tag.
<Admin
menu={createMenus}
loginPage={Login}
dashboard={Dashboard}
appLayout={Layout}
customReducers={{ loginReducer }}
>
3.2 import the login page and login reducers in app.js
import loginReducer from './loginReducer';
import Login from "./Login";