Redux forms: how to handle errors in form component - javascript

Now I have such error:
Unhandled Rejection (SubmissionError): Submit Validation Failed
27 | .catch(err => {
> 28 | return ErrorHandler.raiseAnError(err);
29 | });
Here is my code:
TractorForm.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import { reduxForm } from "redux-form";
import FormInput from "../Shared/FormInput";
import GlobalConst from "../GlobalConst";
import { Link } from "react-router-dom";
import SelectInput from "../Shared/SelectInput";
import ErrorHandler from "../ErrorHandler";
const manufacturers = ["DAF", "VOLVO", "SCANIA", "MAN", "IVECO"];
class TractorFormWrapper extends Component {
state = {
loading: false,
isSubmitted: false
};
onFormSubmit = e => {
e.preventDefault();
this.setState({ isSubmitted: true });
if (this.props.valid) {
this.setState({ loading: true });
this.props
.handleSubmit(e)
.then(() => alert("is ok!"))
.catch(err => {
return ErrorHandler.raiseAnError(err);
});
}
};
render() {
const { submitText, error } = this.props;
const { loading, isSubmitted } = this.state;
const formClassNames = loading ? "ui form loading" : "ui form";
return (
<form className={formClassNames} onSubmit={this.onFormSubmit}>
<div className="ui grid fields">
<div className="sixteen wide eight wide computer column">
<SelectInput
name="manufacturer"
type="text"
label="Производитель"
validations={[GlobalConst.REQUIRED]}
isSubmitted={isSubmitted}
values={manufacturers}
/>
</div>
<div className="sixteen wide eight wide computer column">
<FormInput
name="model"
type="text"
label="Модель"
validations={[GlobalConst.REQUIRED]}
isSubmitted={isSubmitted}
/>
</div>
<div className="sixteen wide column">
<FormInput
name="description"
type="textarea"
label="Описание"
isSubmitted={isSubmitted}
/>
</div>
</div>
{error && (
<div className="ui red message">
<strong>{error}</strong>
</div>
)}
<div className="ui fluid buttons">
<button
className="ui primary button"
type="submit"
disabled={loading}
>
{submitText}
</button>
<Link to="/tractors" className="ui button">
Отмена
</Link>
</div>
</form>
);
}
}
let TractorForm = {};
TractorForm.propTypes = {
submitText: PropTypes.string
};
TractorForm.defaultProps = {
submitText: "Отправить"
};
TractorForm = reduxForm({
form: "tractor"
})(TractorFormWrapper);
export default TractorForm;
TractorAdd.js
import React, { Component } from "react";
import TractorForm from "./TractorForm";
import TractorApi from "./TractorApi";
import { toast } from "react-semantic-toasts";
import ErrorHandler from "../ErrorHandler";
class TractorAdd extends Component {
state = {};
submit = values =>
TractorApi.create(values).then(
() => {
toast({
type: "success",
icon: "truck",
title: "Тягач создан",
description: ""
});
this.props.history.push("/tractors");
},
error => {
return Promise.reject(error);
}
);
render() {
return (
<div>
<TractorForm onSubmit={this.submit} submitText="Создать" />
</div>
);
}
}
export default TractorAdd;
ErrorHandler.js
import { SubmissionError } from "redux-form";
export default {
raiseAnError: error => {
if (
error.response.data.hasOwnProperty("message") &&
error.response.data.hasOwnProperty("stackHighlighted")
) {
throw new SubmissionError({
_error: error.response.data.hasOwnProperty("message") || "error"
});
} else {
const errKeys = Object.keys(error.response.data);
const errObj = {};
for (const errItem of errKeys) {
errObj[errItem] = error.response.data[errItem]["message"];
}
errObj["_error"] = "Произошла ошибка!";
throw new SubmissionError(errObj);
}
}
};
why do I need this in form instead of add component? so I can fix logic with loading state variable and re-submit form
also my _error isn't working for some reasons, but I've done everything as in doc: https://redux-form.com/7.3.0/examples/submitvalidation/
what I do wrong and how to handle SubmissionError in form component?

The reason your error handling is not working is because you should throw new SubmissionError in the handleSubmit function itself, like the example you linked:
<form onSubmit={handleSubmit(submit)}>
function submit(values) {
return sleep(1000).then(() => {
// If error
throw new SubmissionError({
password: 'Wrong password',
_error: 'Login failed!'
})
// The rest logic here ...
})
}
So you should refactor your code a little bit, something like that (follow the comments):
<form className={formClassNames} onSubmit={handleSubmit(values => {
// 1. Your submit logic should be here.
// 2. Better to organize it in a stand-alone function, as `submit` function from the above example.
// 3. If you throw SubmissionError here, the error handling will work.
throw new SubmissionError({
_error: 'Error'
})
)}>
Try to tune-up and simplify your code, like the official library example you provided.
Update 1 - almost a complete example. Please follows the comments strictly:
* I removed some unrelated to the problem code blocks
TractorForm - it will be reused for both Edit and Add (Create) actions.
class TractorForm extends Component {
render() {
const { handleSubmit, error } = this.props;
return (
<form onSubmit={handleSubmit}>
// Rest input fields should be here ...
{ error && (
<div className="ui red message">
<strong>{error}</strong>
</div>
)}
<div className="ui fluid buttons">
<button
className="ui primary button"
type="submit"
disabled={loading}
>
{submitText}
</button>
</div>
</form>
);
}
}
export default reduxForm({
form: "tractor"
})(TractorForm);
TractorAdd - For adding a new Tractor. The same logic, you can apply for Edit Tractor. You have to create a new TractorEdit component, that will pass down onSubmit function to TractorForm.
class TractorAdd extends Component {
onSubmit (values) {
// Please make sure here you return the promise result. It's a `redux-form` gotcha.
// If you don't return it, error handling won't work.
return TractorApi.create(values).then(
() => {
toast({
type: "success",
icon: "truck",
title: "Тягач создан",
description: ""
});
this.props.history.push("/tractors");
},
error => {
// 1. Here you should throw new SubmissionError.
// 2. You should normalize the error, using some parts of `ErrorHandler` function
throw new SubmissionError(error)
}
);
}
render() {
return <div>
<TractorForm onSubmit={this.onSubmit} submitText="Создать" />
</div>
}
}
export default TractorAdd;
Update 2 - keep you implementation as it is, but change a little bit your TractorFormWrapper onFormSubmit and its usage:
Documentation explanations here.
TractorForm
class TractorFormWrapper extends Component {
state = {
loading: false,
isSubmitted: false
};
onFormSubmit = data => {
this.setState({ isSubmitted: true });
if (this.props.valid) {
this.setState({ loading: true });
// `onSubmit` comes from `TractorAdd`
return this.props.onSubmit(data)
.then(() => alert("is ok!"))
.catch(err => {
return ErrorHandler.raiseAnError(err);
});
}
};
render() {
const { handleSubmit } = this.props
return <form onSubmit={handleSubmit(this.onFormSubmit)}>The rest logic is here ...</form>
}
}
let TractorForm = {};
TractorForm.propTypes = {
submitText: PropTypes.string
};
TractorForm.defaultProps = {
submitText: "Отправить"
};
TractorForm = reduxForm({
form: "tractor"
})(TractorFormWrapper);
export default TractorForm;

Related

React Error - Can't perform a React state update on an unmounted component

I have received this error whenever I close my loginPage component. It closes in the socket.on('LOGIN_RESULT') inside the sendLoginRequest function. I need to use a useEffect function but I do not know the correct way to implement it into my loginPage component.
loginPage.js ------
import React from 'react';
import loginstyles from './loginpage.module.css';
import { connect } from 'react-redux';
import { LOGIN } from '../messageStore/actions';
import SectionLoader from '../../components/login/SectionLoader';
import * as SectionGetter from '../../components/login/SectionGetter';
import ClientSocket from '../../socket/ClientSocket';
const loginPage = props => {
//Pop up window that a user can use to login
const socket = ClientSocket.getSocket();
const [loginData, setLoginData] = React.useState({ //Temporary login info before confirmed and push into Redux
email: '',
pass: '',
});
const [loginState, setloginState] = React.useState(SectionGetter.GET_EMAILSECTION(''));
const [sentData, setSentData] = React.useState(false);
const changeLogin = event => { //Changes the temporary login info the user entered
const name = event.target.name;
const value = event.target.value;
setLoginData({
...loginData,
[name]: value
});
}
const validateInfo = () => {
if(loginData.email != "") {
if(loginData.pass != "") {
return true;
} else {
setloginState(SectionGetter.GET_PASSWORDSECTION('Please enter your password'));
return false;
}
} else {
setloginState(SectionGetter.GET_EMAILSECTION('Please enter your email'));
return false;
}
}
function loginIntoApp() {
if(!sentData) {
if(validateInfo()) {
setloginState(SectionGetter.GET_LOADINGSECTION);
setSentData(true);
sendLoginRequest();
}
}
}
function sendLoginRequest() {
socket.emit('LOGIN', {
email: loginData.email,
pass: loginData.pass
});
socket.on('LOGIN_RESULT', data => {
if(data.result === 'SUCCESS') {
props.dispatch(LOGIN(data.username, loginData.email, loginData.pass));
props.dispatch({ type: 'LOGIN_EXIT', payload: null} );
} else {
setloginState(SectionGetter.GET_WRONGDETAILS_SECTION(''));
}
setSentData(false);
});
}
const onNextSection = () => {
if(loginState.type === SectionGetter.PASSWORD) {
loginIntoApp();
return;
}
setloginState(SectionGetter.nextSection(loginState));
}
const exit = () => {
props.dispatch({ type: 'LOGIN_EXIT', payload: null });
}
return (
/*<div className={loginstyles.loginArea}>
<form className={loginstyles.loginform}>
<div className={loginstyles.logincomponent}>
<label><div className={loginstyles.descriptiontext}>Email: </div>
<input type="text" className={loginstyles.loginform_textarea} />
</label><br />
</div>
<div className={loginstyles.logincomponent}>
<label><div className={loginstyles.descriptiontext}>pass: </div>
<input type="pass" className={loginstyles.loginform_textarea} />
</label><br />
</div>
<div className={loginstyles.logincomponent}>
<label>
Remember me <input type="checkbox" name="remember" className={loginstyles.loginform_remember} />
</label>
</div>
<div className={loginstyles.logincomponent} id={loginstyles.login}>
<button type="submit" className={loginstyles.loginform_submit}>Login</button>
</div>
</form>
</div>*/
<div className={loginstyles.loginArea}>
<input type="image" src="/exit.svg" border="0" alt="Submit" onClick={exit} className={loginstyles.exit_image}/>
<SectionLoader section={loginState.section} onNextSection={onNextSection.bind(this)} changeLogin={changeLogin.bind(this)} error={loginState.error} login={loginIntoApp.bind(this)}/>
</div>
);
}
const mapStateToProps = state => {
return {
email: state.loginDetails.email,
password: state.loginDetails.password
};
}
export default connect(mapStateToProps)(loginPage);
index.js ----
function Home(props) {
const setLoginState = state => {
console.log(state);
if(state) {
props.dispatch({ type: 'LOGIN_ACTIVE', payload: null });
} else {
props.dispatch({ type: 'LOGIN_INACTIVE', payload: null });
}
}
return (
<div>
<Head>
<title>AXSHS Live</title>
</Head>
{props.loginActive === true ?
<LoginPage/>
:
null
}
<div className={props.loginActive === true ? homepage.app_loginactive : homepage.app}>
<div className={homepage.home}>
<Navbar username={props.details.username} loginState={setLoginState}/>
<Grid />
</div>
</div>
</div>
);
}
const mapStateToProps = state => {
return {
details: state.loginDetails,
loginActive: state.loginActive
}
}
export default connect(mapStateToProps)(Home);
What is the correct way to implement a React useEffect cleanup function when you are using socket.io and redux. I'm not sure wether its my socket that is causing the react state update or the redux connect function.

createError.js:16 Uncaught (in promise) Error: Request failed with status code 400 in react

iam getting bad request when registering the user and sending the info to the database
iam using axios.post(apiurl.{username: user.username, password: user.password, email :user.email }
and storing this code in registeruser function
and then calling it when a user submits the form
code bellow for registering users
import React, { Component } from "react";
import Form from "./form";
import joi from "joi-browser";
import {registeruser} from "../http/registeruser";
class Register extends Form {
state = {
data:{
name:"", password:"", email:""},
errors:{}
}
schema={
name: joi.string().required().label("name"),
password: joi.string().required().min(5).label("Password"),
email: joi.string().required().label("Email"),
}
doSubmit = async () =>{
await registeruser(this.state.data);
}
render() {
return ( <div>
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
{this.renderInput("name","name","string")}
{this.renderInput("password","Password","password")}
{this.renderInput("email","Email","string")}
{this.renderButton("Register")}
</form>
</div>
);
}
}
export default Register;
You are calling register user function wrong. your data object is not correctly built . I have provided a skeleton here . see if it helps
import React, { Component } from "react";
import Form from "./form";
import joi from "joi-browser";
import {registeruser} from "../http/registeruser";
class Register extends Form {
state = {
data:{
name:"", password:"", email:""},errors:{}
}
schema={
name: joi.string().required().label("name"),
password: joi.string().required().min(5).label("Password"),
email: joi.string().required().label("Email"),
}
}
const handleSubmit = () => {
//validate properly here
schema={
name: joi.string().required().label("name"),
password: joi.string().required().min(5).label("Password"),
email: joi.string().required().label("Email"),
}
// build your data object properly here
await registeruser(this.state.data);
}
render() {
return ( <div>
<h1>Register</h1>
<form onSubmit={ () => this.handleSubmit() }>. // change this function so that you dont need to bind
{this.renderInput("name","name","string")}
{this.renderInput("password","Password","password")}
{this.renderInput("email","Email","string")}
{this.renderButton("Register")}
</form>
</div>
);
}
}
export default Register;
the form component is the one to handlechange and validate data
here iam going to include it
import React, { Component } from "react";
import joi from "joi-browser";
import Input from "./input";
import Select from"../select";
class Form extends Component {
state = {
data: {},
errors:{}
};
validate = ()=>{
const options = { abortEarly : false };
const results = joi.validate(this.state.data,this.schema, options )
if (!results.error) return null;
console.log(results)
const errors = {};
for (let item of results.error.details) errors[item.path[0]] = item.message;
return errors;
}
handleSubmit = e => {
e.preventDefault();
const errors = this.validate();
this.setState({ errors: errors || {} });
if (errors) return;
this.doSubmit();
};
validateProperty = ({name, value}) => {
const obj ={[name] : value};
const schema = {[name] : this.schema[name]}
const {error}= joi.validate(obj,schema);
return error ? error.details[0].message : null ;
}
handleChange = ({ currentTarget: input }) => {
const errors = { ...this.state.errors };
const errorMessage = this.validateProperty(input);
if (errorMessage) errors[input.name] = errorMessage;
else delete errors[input.name];
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data, errors });
};
renderButton(label) {
return <button
disabled={this.validate()}
className="btn-primary btn">{label}</button>
}
renderInput(name, label, type = "text") {
const { data, errors } = this.state;
return (
<Input
type={type}
name={name}
value={data[name]}
label={label}
onChange={this.handleChange}
error={errors[name]}
/>
);
}
renderSelect(name, label, options) {
const { data, errors } = this.state;
return (
<Select
name={name}
value={data[name]}
label={label}
options={options}
onChange={this.handleChange}
error={errors[name]}
/>
);
}
}
export default Form;

ReactJS | Loading State in component doesn't render Spinner

I am trying to make a React component that displays multiple renders based on props and state. So, while I wait for the promise to be resolved, I want to display a spinner Component
Main Renders:
NoResource Component => When the user is not valid
Spinner Component => When is loading on all renders
BasicRender Component => When data are fetched and is not loading
Below is my component:
/* eslint-disable react/prefer-stateless-function */
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { getUser, listUsers } from '../../config/service';
export class UserDetailsScreen extends Component {
static propTypes = {
match: PropTypes.shape({
isExact: PropTypes.bool,
params: PropTypes.object,
path: PropTypes.string,
url: PropTypes.string
}),
// eslint-disable-next-line react/forbid-prop-types
history: PropTypes.object,
label: PropTypes.string,
actualValue: PropTypes.string,
callBack: PropTypes.func
};
state = {
user: {},
error: '',
isloading: false
};
componentDidMount() {
this.fetchUser();
this.setState({ isLoading: true})
}
getUserUsername = () => {
const { match } = this.props;
const { params } = match;
return params.username;
};
fetchUser = () => {
getUser(this.getUserUsername())
.then(username => {
this.setState({
user: username.data,
isloading: false
});
})
.catch(({ message = 'Could not retrieve data from server.' }) => {
this.setState({
user: null,
error: message,
isLoading: false
});
});
};
validateUsername = () =>
listUsers().then(({ data }) => {
const { match } = this.props;
if (data.includes(match.params.username)) {
return true;
}
return false;
});
// eslint-disable-next-line no-restricted-globals
redirectToUsers = async () => {
const { history } = this.props;
await history.push('/management/users');
};
renderUserDetails() {
const { user, error } = this.state;
const { callBack, actualValue, label, match } = this.props;
return (
<div className="lenses-container-fluid container-fluid">
<div className="row">
.. More Content ..
{user && <HeaderMenuButton data-test="header-menu-button" />}
</div>
{user && this.validateUsername() ? (
<Fragment>
.. Content ..
</Fragment>
) : (
<div className="container-fluid">
{this.renderNoResourceComponent()}
</div>
)}
<ToolTip id="loggedIn" place="right">
{user.loggedIn ? <span>Online</span> : <span>Oflline</span>}
</ToolTip>
</div>
);
}
renderNoResourceComponent = () => {
const { match } = this.props;
return (
<div className="center-block">
<NoResource
icon="exclamation-triangle"
title="Ooops.."
primaryBtn="« Back to Users"
primaryCallback={this.redirectToUsers}
>
<h5>404: USER NOT FOUND</h5>
<p>
Sorry, but the User with username:
<strong>{match.params.username}</strong> does not exists
</p>
</NoResource>
</div>
);
};
renderSpinner = () => {
const { isLoading, error } = this.state;
if (isLoading && error === null) {
return <ContentSpinner />;
}
return null;
};
render() {
return (
<div className="container-fluid mt-2">
{this.renderSpinner()}
{this.renderUserDetails()}
</div>
);
}
}
export default withRouter(UserDetailsScreen);
The problem is:
I get the spinner along with the main component, and I am getting this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.. Can you please tell me what I am doing wrong.
The error is because you are running the renderUserDetailsComponent even when your API call is in loading state. You must only render the spinner on loading state
renderUserDetails() {
const { user, error, isLoading } = this.state;
if(isLoading) {
return null;
}
const { callBack, actualValue, label, match } = this.props;
return (
<div className="lenses-container-fluid container-fluid">
<div className="row">
.. More Content ..
{user && <HeaderMenuButton data-test="header-menu-button" />}
</div>
{user && this.validateUsername() ? (
<Fragment>
.. Content ..
</Fragment>
) : (
<div className="container-fluid">
{this.renderNoResourceComponent()}
</div>
)}
<ToolTip id="loggedIn" place="right">
{user.loggedIn ? <span>Online</span> : <span>Oflline</span>}
</ToolTip>
</div>
);
}

how can I show customized error messaged from server side validation in React Admin package?

Is there any way to perform server side form validation using https://github.com/marmelab/react-admin package?
Here's the code for AdminCreate Component. It sends create request to api. Api returns validation error with status code 422 or status code 200 if everything is ok.
export class AdminCreate extends Component {
render() {
return <Create {...this.props}>
<SimpleForm>
<TextInput source="name" type="text" />
<TextInput source="email" type="email"/>
<TextInput source="password" type="password"/>
<TextInput source="password_confirmation" type="password"/>
<TextInput source="phone" type="tel"/>
</SimpleForm>
</Create>;
}
}
So the question is, how can I show errors for each field separately from error object sent from server? Here is the example of error object:
{
errors: {name: "The name is required", email: "The email is required"},
message: "invalid data"
}
Thank you in advance!
class SimpleForm extends Component {
handleSubmitWithRedirect = (redirect = this.props.redirect) =>
this.props.handleSubmit(data => {
dataProvider(CREATE, 'admins', { data: { ...data } }).catch(e => {
throw new SubmissionError(e.body.errors)
}).then(/* Here must be redirection logic i think */);
});
render() {
const {
basePath,
children,
classes = {},
className,
invalid,
pristine,
record,
resource,
submitOnEnter,
toolbar,
version,
...rest
} = this.props;
return (
<form
className={classnames('simple-form', className)}
{...sanitizeRestProps(rest)}
>
<div className={classes.form} key={version}>
{Children.map(children, input => (
<FormInput
basePath={basePath}
input={input}
record={record}
resource={resource}
/>
))}
</div>
{toolbar &&
React.cloneElement(toolbar, {
handleSubmitWithRedirect: this.handleSubmitWithRedirect,
invalid,
pristine,
submitOnEnter,
})}
</form>
);
}
}
Now i have following code, and it's showing validation errors. But the problem is, i can't perform redirection after success. Any thoughts?
If you're using SimpleForm, you can use asyncValidate together with asyncBlurFields as suggested in a comment in issue 97. I didn't use SimpleForm, so this is all I can tell you about that.
I've used a simple form. And you can use server-side validation there as well. Here's how I've done it. A complete and working example.
import React from 'react';
import PropTypes from 'prop-types';
import { Field, propTypes, reduxForm, SubmissionError } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import { CardActions } from 'material-ui/Card';
import Button from 'material-ui/Button';
import TextField from 'material-ui/TextField';
import { CircularProgress } from 'material-ui/Progress';
import { CREATE, translate } from 'ra-core';
import { dataProvider } from '../../providers'; // <-- Make sure to import yours!
const renderInput = ({
meta: { touched, error } = {},
input: { ...inputProps },
...props
}) => (
<TextField
error={!!(touched && error)}
helperText={touched && error}
{...inputProps}
{...props}
fullWidth
/>
);
/**
* Inspired by
* - https://redux-form.com/6.4.3/examples/submitvalidation/
* - https://marmelab.com/react-admin/Actions.html#using-a-data-provider-instead-of-fetch
*/
const submit = data =>
dataProvider(CREATE, 'things', { data: { ...data } }).catch(e => {
const payLoadKeys = Object.keys(data);
const errorKey = payLoadKeys.length === 1 ? payLoadKeys[0] : '_error';
// Here I set the error either on the key by the name of the field
// if there was just 1 field in the payload.
// The `Field` with the same `name` in the `form` wil have
// the `helperText` shown.
// When multiple fields where present in the payload, the error message is set on the _error key, making the general error visible.
const errorObject = {
[errorKey]: e.message,
};
throw new SubmissionError(errorObject);
});
const MyForm = ({ isLoading, handleSubmit, error, translate }) => (
<form onSubmit={handleSubmit(submit)}>
<div>
<div>
<Field
name="email"
component={renderInput}
label="Email"
disabled={isLoading}
/>
</div>
</div>
<CardActions>
<Button
variant="raised"
type="submit"
color="primary"
disabled={isLoading}
>
{isLoading && <CircularProgress size={25} thickness={2} />}
Signin
</Button>
{error && <strong>General error: {translate(error)}</strong>}
</CardActions>
</form>
);
MyForm.propTypes = {
...propTypes,
classes: PropTypes.object,
redirectTo: PropTypes.string,
};
const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
const enhance = compose(
translate,
connect(mapStateToProps),
reduxForm({
form: 'aFormName',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.email)
errors.email = translate('ra.validation.required');
return errors;
},
})
);
export default enhance(MyForm);
If the code needs further explanation, drop a comment below and I'll try to elaborate.
I hoped to be able to do the action of the REST-request by dispatching an action with onSuccess and onFailure side effects as described here, but I couldn't get that to work together with SubmissionError.
Here one more solution from official repo.
https://github.com/marmelab/react-admin/pull/871
You need to import HttpError(message, status, body) in DataProvider and throw it.
Then in errorSaga parse body to redux-form structure.
That's it.
Enjoy.
Found a working solution for react-admin 3.8.1 that seems to work well.
Here is the reference code
https://codesandbox.io/s/wy7z7q5zx5?file=/index.js:966-979
Example:
First make the helper functions as necessary.
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const simpleMemoize = fn => {
let lastArg;
let lastResult;
return arg => {
if (arg !== lastArg) {
lastArg = arg;
lastResult = fn(arg);
}
return lastResult;
};
};
Then the actual validation code
const usernameAvailable = simpleMemoize(async value => {
if (!value) {
return "Required";
}
await sleep(400);
if (
~["john", "paul", "george", "ringo"].indexOf(value && value.toLowerCase())
) {
return "Username taken!";
}
});
Finally wire it up to your field:
const validateUserName = [required(), maxLength(10), abbrevUnique];
const UserNameInput = (props) => {
return (
<TextInput
label="User Name"
source="username"
variant='outlined'
validate={validateAbbrev}
>
</TextInput>);
}
In addition to Christiaan Westerbeek's answer.
I just recreating a SimpleForm component with some of Christian's hints.
In begining i tried to extend SimpleForm with needed server-side validation functionality, but there were some issues (such as not binded context to its handleSubmitWithRedirect method), so i just created my CustomForm to use it in every place i need.
import React, { Children, Component } from 'react';
import PropTypes from 'prop-types';
import { reduxForm, SubmissionError } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import { withStyles } from '#material-ui/core/styles';
import classnames from 'classnames';
import { getDefaultValues, translate } from 'ra-core';
import FormInput from 'ra-ui-materialui/lib/form/FormInput';
import Toolbar from 'ra-ui-materialui/lib/form/Toolbar';
import {CREATE, UPDATE} from 'react-admin';
import { showNotification as showNotificationAction } from 'react-admin';
import { push as pushAction } from 'react-router-redux';
import dataProvider from "../../providers/dataProvider";
const styles = theme => ({
form: {
[theme.breakpoints.up('sm')]: {
padding: '0 1em 1em 1em',
},
[theme.breakpoints.down('xs')]: {
padding: '0 1em 5em 1em',
},
},
});
const sanitizeRestProps = ({
anyTouched,
array,
asyncValidate,
asyncValidating,
autofill,
blur,
change,
clearAsyncError,
clearFields,
clearSubmit,
clearSubmitErrors,
destroy,
dirty,
dispatch,
form,
handleSubmit,
initialize,
initialized,
initialValues,
pristine,
pure,
redirect,
reset,
resetSection,
save,
submit,
submitFailed,
submitSucceeded,
submitting,
touch,
translate,
triggerSubmit,
untouch,
valid,
validate,
...props
}) => props;
/*
* Zend validation adapted catch(e) method.
* Formatted as
* e = {
* field_name: { errorType: 'messageText' }
* }
*/
const submit = (data, resource) => {
let actionType = data.id ? UPDATE : CREATE;
return dataProvider(actionType, resource, {data: {...data}}).catch(e => {
let errorObject = {};
for (let fieldName in e) {
let fieldErrors = e[fieldName];
errorObject[fieldName] = Object.values(fieldErrors).map(value => `${value}\n`);
}
throw new SubmissionError(errorObject);
});
};
export class CustomForm extends Component {
handleSubmitWithRedirect(redirect = this.props.redirect) {
return this.props.handleSubmit(data => {
return submit(data, this.props.resource).then((result) => {
let path;
switch (redirect) {
case 'create':
path = `/${this.props.resource}/create`;
break;
case 'edit':
path = `/${this.props.resource}/${result.data.id}`;
break;
case 'show':
path = `/${this.props.resource}/${result.data.id}/show`;
break;
default:
path = `/${this.props.resource}`;
}
this.props.dispatch(this.props.showNotification(`${this.props.resource} saved`));
return this.props.dispatch(this.props.push(path));
});
});
}
render() {
const {
basePath,
children,
classes = {},
className,
invalid,
pristine,
push,
record,
resource,
showNotification,
submitOnEnter,
toolbar,
version,
...rest
} = this.props;
return (
<form
// onSubmit={this.props.handleSubmit(submit)}
className={classnames('simple-form', className)}
{...sanitizeRestProps(rest)}
>
<div className={classes.form} key={version}>
{Children.map(children, input => {
return (
<FormInput
basePath={basePath}
input={input}
record={record}
resource={resource}
/>
);
})}
</div>
{toolbar &&
React.cloneElement(toolbar, {
handleSubmitWithRedirect: this.handleSubmitWithRedirect.bind(this),
invalid,
pristine,
submitOnEnter,
})}
</form>
);
}
}
CustomForm.propTypes = {
basePath: PropTypes.string,
children: PropTypes.node,
classes: PropTypes.object,
className: PropTypes.string,
defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
handleSubmit: PropTypes.func, // passed by redux-form
invalid: PropTypes.bool,
pristine: PropTypes.bool,
push: PropTypes.func,
record: PropTypes.object,
resource: PropTypes.string,
redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
save: PropTypes.func, // the handler defined in the parent, which triggers the REST submission
showNotification: PropTypes.func,
submitOnEnter: PropTypes.bool,
toolbar: PropTypes.element,
validate: PropTypes.func,
version: PropTypes.number,
};
CustomForm.defaultProps = {
submitOnEnter: true,
toolbar: <Toolbar />,
};
const enhance = compose(
connect((state, props) => ({
initialValues: getDefaultValues(state, props),
push: pushAction,
showNotification: showNotificationAction,
})),
translate, // Must be before reduxForm so that it can be used in validation
reduxForm({
form: 'record-form',
destroyOnUnmount: false,
enableReinitialize: true,
}),
withStyles(styles)
);
export default enhance(CustomForm);
For better understanding of my catch callback: In my data provider i do something like this
...
if (response.status !== 200) {
return Promise.reject(response);
}
return response.json().then((json => {
if (json.state === 0) {
return Promise.reject(json.errors);
}
switch(type) {
...
}
...
}
...

react-stripe-elements Error: You must provide a Stripe Element or a valid token type to create a Token

I am using react-stripe-elements to create a token for payments. However, according to the documentation when the card form is wrapped in the Elements component it should automatically pickup which stripe elements to tokenize.
However, in this case we are presented with the error
You must provide a Stripe Element or a valid token type to create a Token.
Here is the code:
import React from 'react';
import {CardCVCElement, CardExpiryElement, CardNumberElement, PostalCodeElement, StripeProvider, Elements} from 'react-stripe-elements';
class CheckoutForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(ev) {
ev.preventDefault();
this.props.stripe.createToken({email: 'test#test.com'}).then(({token }) => {console.log('Received Stripe token:', token)});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Card details
<CardNumberElement />
<CardExpiryElement />
<CardCVCElement />
<PostalCodeElement />
</label>
<button>Confirm order</button>
</form>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = { stripe: null };
}
componentDidMount() {
this.setState({ stripe: window.Stripe('test_key') });
}
render() {
return (
<StripeProvider stripe={this.state.stripe}>
<Elements>
<CheckoutForm stripe={this.state.stripe} />
</Elements>
</StripeProvider>
);
}
}
export default App;
According to the documentation the following should be true:
'Within the context of Elements, this call to createToken knows which Element to tokenize, since there's only one in this group.'
However, this doesn't seem to be the case. I have also tried using the single 'Card Element' and have not found any success in doing so.
It turns out I never managed to solve the issue using react-stripe-elements. I ended using the standard JS version (from the stripe documentation). Here is my current working solution:
import React from 'react';
class CheckoutForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
elements: null,
card: null
};
}
componentWillReceiveProps() {
this.setState({ elements: this.props.stripe.elements() }, () => {
this.setState({ card: this.state.elements.create('card') }, () => {
this.state.card.mount('#card-element');
});
});
}
handleSubmit(ev) {
ev.preventDefault();
this.props.stripe.createToken(this.state.card).then((token) => {
console.log('Received Stripe token:', token);
});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="row">
<label >
Credit or debit card
</label>
<div id="card-element"/>
<div id="card-errors" role="alert"/>
</div>
<button>Submit Payment</button>
</form>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {stripe: window.Stripe('test_key')};
}
render() {
return (
<CheckoutForm stripe={this.state.stripe}/>
);
}
}
export default App;
In the comments they rightly say you need to use the HOC injectStripe.
The docs for stripe.createToken mention that you need to pass the element you wish to tokenize data from.
Also from the github repo README:
⚠️ NOTE injectStripe cannot be used on the same element that renders the Elements component; it must be used on the child component of Elements. injectStripe returns a wrapped component that needs to sit under but above any code where you'd like to access this.props.stripe.
In my specif case I was using a Mobx store and I needed to handle createToken and my form submission in the same place.
Even though I had a reference to stripe since initialisation it didn't work.
The createToken call needs to come from a component child of Elements and with stripe injected.
I ended up having:
#inject('signupStore')
#observer
class CardInput extends React.Component {
componentDidMount() {
const { signupStore } = this.props;
const handleCard = async name => {
return await this.props.stripe.createToken({ name: name });
};
signupStore.assignHandleCard(handleCard);
}
render() {
return (
<label>
Card details
<CardElement style={{ base: { fontSize: '18px' } }} />
</label>
);
}
}
export default injectStripe(CardInput);
Passing the handler back to the store, and then using it from there.
Part of signupStore:
#action
async submitForm(formValues) {
if (this.stripe && this.handleCard) {
const tokenResponse = await this.handleCard(
`${formValues.firstName} ${formValues.lastName}`
);
runInAction(() => {
console.log('Card token received ', tokenResponse);
if (tokenResponse) {
this.cardToken = tokenResponse.token.id;
formValues.cardToken = this.cardToken;
}
});
const response = await request.signup.submit(formValues);
return response;
}
return null;
}
With the new #stripe/react-stripe-js library it's a bit different. We need to use ElementsConsumer component. Load stripe using loadStripe method and use Elements component to use your form with Stripe.
Here is a basic example.
import { Elements, loadStripe } from "#stripe/react-stripe-js"
const stripePromise = loadStripe(STRIPEKEY)
<Elements stripe={stripePromise}>
<CardForm />
</Elements>
CardForm.js
import {
CardNumberElement,
CardExpiryElement,
CardCvcElement,
ElementsConsumer,
} from "#stripe/react-stripe-js"
const StripeForm = ({ stripe, elements }) => {
const handleSubmit = async () => {
if (!stripe || !elements) {
return
}
const cardNumberElement = elements.getElement(CardNumberElement)
const res = await stripe.createToken(cardNumberElement)
}
return (
<form>
<div>
<label htmlFor="cardNumber">Card Number</label>
<div>
<CardNumberElement />
</div>
</div>
<div>
<label htmlFor="cardName">Card Name</label>
<input
type="text"
name="cardName"
required
placeholder="Please Enter"
pattern="[A-Za-z]"
/>
</div>
<div>
<label htmlFor="expDate">Exp. Date</label>
<div>
<CardExpiryElement />
</div>
</div>
<div>
<label htmlFor="CVC">CVC</label>
<div>
<CardCvcElement />
</div>
</div>
</form>
)
}
const CardForm = () => {
return (
<ElementsConsumer>
{({ stripe, elements }) => (
<StripeForm stripe={stripe} elements={elements} />
)}
</ElementsConsumer>
)
}
export default CardForm
React js it's working for me
Card component , Get error , Card Detail and Generate Token
import React, { useState, useEffect } from "react";
import {loadStripe} from '#stripe/stripe-js';
import {CardElement,Elements,useStripe,useElements} from '#stripe/react-stripe-js';
const stripePromise = loadStripe('pk_test_YOUR_STRIPE_KYE');
const CheckoutForm = () => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
event.preventDefault();
const {error, paymentMethod} = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
});
console.log("paymentMethod",paymentMethod);
console.log("error", error);
if (paymentMethod) {
const cardElement = elements.getElement(CardElement);
let token = await stripe.createToken(cardElement);
console.log(token);
}
};
return (
<div>
<form onSubmit={ handleSubmit }>
<div className="login-box" id="step2" >
<div className="form-row">
<label for="card-element" style={ { color:" #76bbdf" } }>
Credit or debit card
</label>
</div>
<div >
<CardElement
className="StripeElement"
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
}}
/>
</div>
<button name="submintbtn2" className="btn btn-primary" > SUBSCRIBE </button>
</div>
</form>
</div>
)};
const Registration = () => (
<div>
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
</div>
);
export default Registration;

Categories