I am new to redux-form and I'm having a strange issue with handling onSubmit.
When I set up my project exactly as in the redux-form example here http://redux-form.com/6.7.0/examples/syncValidation/ it works as expected. I have attempted to extend this example for my needs and have confirmed that it works as expected when it loads the form like so: route component > form.
The issue arises when I attempt to load the form within a react component which is loaded via a route (route component > container component > form). When I click submit the field values are added to the address bar and form validation doesn't run. I have tried absolutely everything I can think of to fix this. The code provided below will work correctly if you replace <Main /> with <RegisterForm handleSubmit={showResults} /> in index.js. Any ideas where I'm going wrong here?
RegisterForm.js:
import React from 'react';
import { Field, reduxForm } from 'redux-form';
const validate = values => {
const errors = {};
if (!values.name) {
errors.name = 'Required';
} else if (values.name.length <= 2) {
errors.username = 'Must be 2 characters or more';
} else if (values.name.length > 50) {
errors.username = 'Must be 50 characters or less';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z] {2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
if (!values.password) {
errors.password = 'Required';
} else if (!values.confirm) {
errors.confirm = 'Required';
} else if (values.password !== values.confirm) {
errors.confirm = 'Passwords do not match';
}
return errors;
};
const renderField = ({ input, label, type, id, meta: { touched, error, warning } }) => (
<div>
<label htmlFor={id}>{label}</label>
<div>
<input {...input} id={id} placeholder={label} type={type} />
{touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
</div>
</div>
);
const RegisterForm = (props) => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="medium-6 columns medium-centered">
<Field type="text" id="name" name="name" component={renderField} placeholder="name" label="Name" />
</div>
<div className="medium-6 columns medium-centered">
<Field type="text" id="email" name="email" component={renderField} placeholder="email" label="Email" />
</div>
<div className="medium-6 columns medium-centered">
<Field type="password" id="password" name="password" component={renderField} placeholder="password" label="Password" />
</div>
<div className="medium-6 columns medium-centered">
<Field type="password" id="confirm" name="confirm" component={renderField} placeholder="confirm" label="Confirm password" />
</div>
<div className="medium-6 columns medium-centered">
<button type="submit" disabled={submitting}>Submit</button>
</div>
</div>
</form>
);
};
export default reduxForm({
form: 'register', // a unique identifier for this form
validate,
})(RegisterForm);
Index.js(works):
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { HashRouter as Router, hashHistory } from 'react-router-dom';
const store = require('./store').configure();
import RegisterForm from './RegisterForm.jsx';
import Main from './Main.jsx';
const rootEl = document.getElementById('app');
const showResults = (values) => {
window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`);
}
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<div style={{ padding: 15 }}>
<h2>Synchronous Validation</h2>
<RegisterForm handleSubmit={showResults} />
</div>
</Router>
</Provider>,
rootEl,
);
Index.js(doesn't work):
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { HashRouter as Router, hashHistory } from 'react-router-dom';
const store = require('./store').configure();
import RegisterForm from './RegisterForm.jsx';
import Main from './Main.jsx';
const rootEl = document.getElementById('app');
const showResults = (values) => {
window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`);
}
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<div style={{ padding: 15 }}>
<h2>Synchronous Validation</h2>
<Main />
</div>
</Router>
</Provider>,
rootEl,
);
Main.js:
import React, { Component } from 'react';
import RegisterForm from './RegisterForm.jsx';
const showResults = (values) => {
window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`);
};
class Register extends Component {
render() {
return (
<RegisterForm handleSubmit={showResults} />
);
}
}
export default Register;
You should pass in your submit handler to the onSubmit prop, not handleSubmit. It comes in to your form component as handleSubmit, so that code should be fine.
class Register extends Component {
render() {
return (
//change this
<RegisterForm onSubmit={showResults} />
);
}
}
Related
I couldn't find any solution in any other questions similar to mine so I decided to ask you for help.
I have tests to my <Login/> page:
import React from 'react';
import { render } from '../../__test__/utils';
import Login from '.';
test('Renders the Login page', () => {
const { getByTestId } = render(<Login />);
expect(getByTestId('login-page')).toBeInTheDocument(); //WORKS
});
test('Renders disabled submit button by default', () => {
const { getByTestId } = render(<Login />);
expect(getByTestId('login-button')).toHaveAttribute('disabled'); // ERROR HERE
});
test('should match base snapshot', () => {
const { asFragment } = render(<Login />);
expect(asFragment()).toMatchSnapshot(); //WORKS
});
I am using the 'utils.js' in my tests because I want to wrap every testing component in the providers I am using. Here is how the utils file looks like:
import React, { useState } from 'react';
import { render as rtlRender } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import { renderRoutes } from 'react-router-config';
import { Router } from 'react-router-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { ThemeProvider } from '#material-ui/styles';
import { createMemoryHistory } from 'history';
import StylesProvider from '../components/StylesProvider';
import routes from '../routes';
import reducer from '../reducers';
import { theme } from '../theme';
const history = createMemoryHistory();
const render = (
ui,
{ initialState, store = createStore(reducer, initialState), ...renderOptions } = {}
) => {
const Wrapper = ({ children }) => {
const [direction] = useState('ltr');
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<StylesProvider direction={direction}>
<Router history={history}>
{children}
{renderRoutes(routes)}
</Router>
</StylesProvider>
</ThemeProvider>
</Provider>
);
};
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
};
export * from '#testing-library/react';
export { render };
Unfortunately, I cannot show the whole app code so I'll make it short. The <Login /> component looks like below:
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import {
Card, CardContent, CardHeader, Typography, Divider, Grid
} from '#material-ui/core';
import Page from 'src/components/Page';
import LoginForm from './LoginForm';
const Login = () => (
<Page className={classes.root} title="Login" dataTestId="login-page">
//here the component body//
<LoginForm
afterLoginRoute="/overview"
loginEndpoint="login"
userRole={userRoles.USER}
className={classes.loginForm}
/>
</Page>
)
In the <LoginForm /> component I have a Material-UI button with data-testid="login-button"
LoginForm:
return (
<div>
<Snackbar open={loginStatus.error} autoHideDuration={6000}>
<Alert severity="error">
Se ha producido un error, comprueba los credenciales y prueba de nuevo.
</Alert>
</Snackbar>
<form {...rest} className={clsx(classes.root, className)} onSubmit={handleSubmit}>
<div className={classes.fields}>
<TextField
className={classes.input}
error={hasError('email')}
fullWidth
helperText={hasError('email') ? formState.errors.email[0] : null}
label="Email"
name="email"
onChange={handleChange}
value={formState.values.email || ''}
variant="outlined"
/>
<TextField
className={classes.input}
error={hasError('password')}
fullWidth
helperText={hasError('password') ? formState.errors.password[0] : null}
label="Contraseña"
name="password"
onChange={handleChange}
type="password"
value={formState.values.password || ''}
variant="outlined"
/>
</div>
<Button
className={classes.submitButton}
color="secondary"
disabled={!formState.isValid}
size="large"
type="submit"
variant="contained"
data-testid="login-button"
>
Entrar
</Button>
</form>
</div>
);
Current test result I have is TestingLibraryElementError: Found multiple elements by: [data-testid="login-button"]. I am out of ideas right now. Could this happen because I try to pass the data-testid prop to the <Button /> which is imported from Material-UI library? Then, shouldn't I see other error message? Please help.
I have to create is a registration form that consists of full name, email, password, mobile number, DOB and once the email validation is done and once the submit button is clicked, it should direct me to the login page. The login page has an email and password (the details from registration need not match the login) and once the button is clicked(after the validation) it should display "Welcome". I'm sharing the files here. Used, react-router, Material UI.
Login.js
/* eslint-disable no-undef */
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Grid, Paper, TextField } from "#material-ui/core";
import { Button } from "#material-ui/core";
import Welcome from './Welcome';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
errors: {},
};
this.validate = this.validate.bind(this);
}
validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
validate() {
const { email, password, errors } = this.state;
if (!this.validateEmail(email)) {
errors.email = "Provide valid email";
}
if (password.length > 5) {
errors.password = "Provide min 5-digit password";
}
if (errors) {
this.setState({ errors });
} else {
this.props.location.push('/Welcome');
}
}
render() {
const paperStyle = {
padding: 20,
height: "70vh",
width: 280,
margin: "20px auto",
};
return (
<Grid>
<Paper elevation={10} style={paperStyle}>
<Grid align="center">
<h2>Login</h2>
</Grid>
<TextField
error={errors.email}
label="Email"
placeholder="Enter mail"
fullwidth
value={email}
onChange={(e) => this.setState({ email: e.target.value })}
required
helperText={errors.password}
/>
<TextField
error={errors.password}
label="Password"
placeholder="Enter password"
type="password"
fullwidth
required
value={password}
onChange={(e) => this.setState({ password: e.target.value })}
helperText={errors.password}
/>
{/*<Link to="/Welcome">*/}
<Button variant="contained" color="primary" onClick={this.validate}>
Login
</Button>
{/*</Link> */}
</Paper>
</Grid>
);
}
}
export default Login;
Registration.js
/* eslint-disable no-undef */
import React, { Component} from 'react';
import { Link } from "react-router-dom";
import { Grid, Paper, TextField } from "#material-ui/core";
import { Button } from "#material-ui/core";
import Login from './Login';
class Registration extends Component {
constructor(props) {
super(props);
this.state = {
fullName: "",
mobileNumber: "",
email: "",
password: "",
errors: {},
};
this.validate = this.validate.bind(this);
}
validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
validate() {
const { fullName, email, password, errors } = this.state;
if (!this.validateEmail(email)){
errors.email = "Provide valid email";
}
if (password.length > 5){
errors.password = "Provide min 5-digit password";
}
if (errors) {
this.setState({ errors });
} else {
this.props.location.push('/Login');
}
}
render() {
const paperStyle = {
padding: 20,
height: "70vh",
width: 280,
margin: "20px auto",
};
return (
<Grid>
<Paper elevation={10} style={paperStyle}>
<Grid align="center">
<h2>Login</h2>
</Grid>
<TextField
label="Full name"
placeholder="Enter name"
fullwidth
value={fullName}
onChange={(e) => this.setState({ fullName: e.target.value })}
required
helperText={errors.fullName}
/>
<TextField
type = 'number'
label="Mobile number"
placeholder="Enter mobile number"
fullwidth
// eslint-disable-next-line no-undef
value={mobileNumber}
onChange={(e) => this.setState({ mobileNumber: e.target.value })}
required
helperText={errors.mobileNumber}
/>
<TextField
error={errors.email}
label="Email"
placeholder="Enter mail"
fullwidth
value={email}
onChange={(e) => this.setState({ email: e.target.value })}
required
helperText={errors.email}
/>
<TextField
error={errors.password}
label="Password"
placeholder="Enter password"
type="password"
fullwidth
required
value={password}
onChange={(e) => this.setState({ password: e.target.value })}
helperText={errors.password}
/>
{/*<Link to="/Login">*/}
<Button variant="contained" color="primary" onClick={this.validate}>
Register here
</Button>
{/*</Link>*/}
</Paper>
</Grid>
);
}
}
export default Registration;
App.js
import React, { Component } from "react";
import "./App.css";
import Login from "./Login";
import Registration from "./Registration";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Welcome from "./Welcome";
import { Typography } from "#material-ui/core";
class App extends Component {
render() {
return (
<Router>
<Typography>
<div className="App">
<Switch>
<Route path="/" exact component={Registration} />
<Route path="/Login" component={Login} />
<Route path="/Welcome" component={Welcome} />
</Switch>
</div>
</Typography>
</Router>
);
}
}
export default App;
Welcome js
import React from "react";
function Welcome() {
return <h1>Welcome</h1>;
}
export default Welcome;
You need add const { fullName, email, password, errors } = this.state; in the render and before return
This is my first post.
I've been reading a lot about functional components and trying everything that I could, but nothing seems to work in my case. I am getting instance.render is not a function.
Hopefully some of you can see where my error is, as I am quite new to programming. This is my code:
App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import Login from "./components/Login"
import Feed from "./components/Feed"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom"
const App = () => {
const [isLoggedIn, setLoggedIn] = useState(false)
const handleLogin = token => {
if (!token) return
localStorage.setItem('token', token)
setLoggedIn(true)
}
const handleLogout = () => () => {
setLoggedIn(false)
localStorage.clear()
}
return (
<div className="App">
<Router>
<Feed isLoggedIn={isLoggedIn} logout={handleLogout} />
<Switch>
<Route
exact
path='/login'
component={(props) => (
<Login {...props} onLogin={handleLogin} />
)}>
</Route>
</Switch>
</Router>
</div>
)
}
export default App;
Login.js
import React from "react"
import axios from "axios"
import { Button, Form, FormGroup, Input } from 'reactstrap'
import "../css/Login.css"
import { Link, BrowserRouter as Router, Switch, Route } from "react-router-dom"
import Signin from "./Signin"
class Login extends React.Component {
constructor (props) {
super(props)
this.state = {
user_name: "",
password: "",
error: false,
loggedIn: false,
}
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
})
}
login = (event) => {
event.preventDefault()
const { user_name, password } = this.state
axios("http://localhost:7001/api/login", {
method: "POST",
data: {
user_name,
password
}
})
.then((response) => {
this.props.onLogin(response.data.token)
this.setState({ loggedIn: true })
this.feedRedirect()
console.log(response.data)
})
.catch((error) => {
console.log(error)
})
this.setState({
user_name: "",
password: "",
error: false,
})
}
feedRedirect = () => {
this.props.history.push('/feed')
}
render() {
const { user_name, password, error, loggedIn } = this.state
return (
<div className="login">
<Form className="login-container" onSubmit={this.login}>
<FormGroup>
<Input
value={this.state.user_name}
onChange={this.handleChange}
name="user_name"
type="text"
className="form-control mb-2"
placeholder="Username"
/>
</FormGroup>
<FormGroup>
<Input
value={this.state.password}
onChange={this.handleChange}
name="password"
type="password"
className="form-control mb-2"
placeholder="Password"
/>
</FormGroup>
<Button className="button-login" disabled={!user_name || !password}>
Log in
</Button>
<hr />
<Router>
<Link to="/signin"> Don't have an account? Sign up here</Link>
<Switch>
<Route path="/signin" component={Signin}>
</Route>
</Switch>
</Router>
</Form>
</div>
)
}
}
export default Login;
Any help will be appreciated. Thank you in advance.
I'm trying to use my custom TextField in my RegisterForm with Yup but he's dosnt work.
All time I have a message "⚠ Champ obligatoire." after click on Submit I don't understand why but is good with a simple input.
RegisterPage.js
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
import Button from "../../lib/Button";
import TextField from "../../lib/TextField";
import * as yup from "yup";
const SignupSchema = yup.object().shape({
firstName: yup.string().required("⚠ Champ obligatoire."),
});
export default function Home() {
const { register, handleSubmit, errors, watch } = useForm({
validationSchema: SignupSchema,
});
const onSubmit = (data) => console.log(data);
console.log(errors);
return (
<div style={styles.inputForm}>
<p>Inscription</p>
<form style={{ marginTop: "40%" }} onSubmit={handleSubmit(onSubmit)}>
<label style={styles.label} htmlFor="firstName">
Prénom
</label>
<TextField
style={styles.input}
name="firstName"
placeholder="Toto"
type="text"
ref={register}
/>
<br />
{errors.firstName && (
<p style={styles.error}>{errors.firstName.message}</p>
)}
<br />
<Button
style={{ marginTop: 10 }}
type="submit"
onClick={handleSubmit(onSubmit)}>
Termine ton incription
</Button>
</form>
</div>
);
}
My CustomTextField
CustomTextfield.js
import React from "react";
import PropTypes from "prop-types";
import TextField from "#material-ui/core/TextField";
function CustomField({ InputLabelProps = {}, ...props }) {
return (
<TextField
InputLabelProps={{ shrink: true, ...InputLabelProps }}
{...props}
/>
);
}
CustomField.propTypes = {
classes: PropTypes.object.isRequired,
};
export default CustomField;
Thanks in advance!
You need to use inputRef instead of ref on TextField. ref will be applied to the outermost element which will be the div rendered by FormControl; and this won't be any help with the yup integration. inputRef will forward the ref to the input element.
<TextField
style={styles.input}
name="firstName"
placeholder="Toto"
type="text"
inputRef={register}
/>
Related documentation: https://material-ui.com/api/text-field/#props
Hi with the app react below I have to manage a form to verify the login to a page, the parameters are email and password but when I execute the code the following error is printed,error: Line 33: 'Form' is not defined react/jsx-no-undef
Javascript code:
import React, { Component } from 'react';
import { Button, FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import logo from './logo.svg';
import './Home.css';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
validateForm() {
return this.state.email.length > 0 && this.state.password.length > 0;
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
handleSubmit = event => {
event.preventDefault();
}
render() {
return (
<div className="Login">
<Form onSubmit={this.handleSubmit}>
<Form.Group controlId="email" bsSize="large">
<Form.Control
autoFocus
type="email"
value={this.state.email}
onChange={this.handleChange}
/>
</Form.Group>
<Form.Group controlId="password" bsSize="large">
<Form.Control
value={this.state.password}
onChange={this.handleChange}
type="password"
/>
</Form.Group>
<Button
block
bsSize="large"
disabled={!this.validateForm()}
type="submit"
>
Login
</Button>
</Form>
</div>
);
}
}
export default Home;
Seems like you forgot to import Form, just add it:
import { Form, Button, FormGroup, FormControl, ControlLabel } from "react-bootstrap";