Change the color of a button based on Formik/Yup Validation - javascript

There is a field where the user must introduce a text. If the text is the desired one, a button should change its color so the user knows the text is the correct one.
So far I've done the validation of the text, an error message is shown if the text is not the desired one but I don't know how to connect this to the button's styling.
Here is my code:
import React from 'react';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import * as Yup from 'yup';
import { Formik, Form, Field } from 'formik';
import { Button, Modal, Input } from 'semantic-ui-react';
import { Creators } from '../../../actions';
import './FridgeForm.scss';
class CreateFridgeForm extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
redirectCreate: false,
};
}
componentDidMount() {
const { edit, getFridge, fridge, getFridges } = this.props;
if (edit) {
getFridge(fridge._id);
getFridges();
}
}
onOpen = () => {
this.setState({ open: true });
};
closeModal = () => {
this.setState({ open: false });
};
handleDelete = values => {
const { deleteFridge, fridge, getFridges } = this.props;
deleteFridge(fridge._id);
this.setState({ open: false });
this.setState({ redirectCreate: true });
getFridges();
};
render() {
const { trigger } = this.props;
const title = 'Do you want to delete a fridge?';
const { open, redirectCreate } = this.state;
if (redirectCreate) {
return <Redirect to="/fridges" />;
}
const initialValues = {
deleteText: '',
};
const wrongTextMessage =
'You must write DELETE in order to delete the fridge';
const requiredErrorMessage = 'This field is required';
const deleteTextValidation = /DELETE\b/g;
const validationSchema = Yup.object({
deleteText: Yup.string()
.matches(deleteTextValidation, wrongTextMessage)
.required(requiredErrorMessage),
});
return (
<Modal
open={open}
trigger={trigger}
onOpen={this.onOpen}
onClose={this.closeModal}
size="small">
<Modal.Header >{title}</Modal.Header>
<Modal.Content >
<Modal.Description >
Are you sure you want to delete?
</Modal.Description>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleDelete(values)}>
{({ values, errors, touched, setFieldValue }) => (
<Form>
<Field
className="add-fridge-input"
name="deleteText"
as={Input}
placeholder="Write DELETE"
/>
<div className="add-fridge-error">
{touched.deleteText && errors.deleteText
? errors.deleteText
: null}
</div>
<Button className="delete-modal-button" type="submit"> // here is the button
Confirm
</Button>
</Form>
)}
</Formik>
</Modal.Content>
</Modal>
);
}
}
const mapStateToProps = state => ({
fridges: state.fridges.fridges,
fridge: state.fridges.selectedFridge,
});
const mapDispatchToProps = {
getFridges: Creators.getFridgesRequest,
getFridge: Creators.getFridgeRequest,
deleteFridge: Creators.deleteFridgeRequest,
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateFridgeForm);
Is there a way to notify the button if validationSchema validates as correct the text?

Related

How to edit react content dynamically

Ok..first things first:
Please refer the image of webApp attached:
My Application displays loginIdCard's consisting(website,username,password) from mongoDb which
i can edit from react when clicking on edit button.
What i did is initially i maintained a editMode key in component state a set it as false.
when a user clicks on edit button the LoginIdCard becomes editable and on clicking save button new values are set in component state and then editLoginId function is dispatch which updates this new value in database.
Now,
following are the things i want:
Initially when edit button is clicked, the value inside the input field should be the original values,
but now it is show empty.
2.The new values should be displayed immediately without rerendering of component.
Note: Now,after cliciking on save button , the component rerenders and the endpoint api return res data which is not a array, so the LoginDisplay component is not able to map and gives this.map is not a function error.
Please Help me
Web app rendering LoginIdCard in LoginDisplay Component
"LoginDispaly Component:Here LoginIdCard Component Will Rendender"
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import LoginIdCard from "./LoginIdCard";
import EditLoginIdComponent from "./EditLoginIdComponent";
import { fetchLoginIds } from "../actions/loginInIdsAction";
import "../css/displaySection.css";
class LoginsDisplay extends Component {
componentWillMount() {
this.props.fetchLoginIds();
}
render() {
const { logins } = this.props;
return (
<div className="display-section">
{logins.map((logins) => (
<LoginIdCard logins={logins} />
))}
</div>
);
}
}
function mapStateToProps(state) {
return {
logins: state.logins.logins,
};
}
LoginsDisplay.propTypes = {
logins: PropTypes.array.isRequired,
};
export default connect(mapStateToProps, { fetchLoginIds })(LoginsDisplay);
"LoginIdCard Component it will be render in LoginDispaly Component"
import React, { Component } from "react";
import { connect } from "react-redux";
import { editLoginId } from "../actions/loginInIdsAction";
import "../css/card.css";
class LoginIdCard extends Component {
constructor(props) {
super(props);
this.state = {
website: "",
username: "",
password: "",
editMode: false,
};
this.handleChange = this.handleChange.bind(this);
// this.handleSave = this.handleChange.bind(this);
}
handleChange = (fieldName, val) => {
console.log(val);
this.setState({
[fieldName]: val,
});
};
handleSave = () => {
const { website, username, password } = this.state;
const { logins } = this.props;
this.props.dispatch(editLoginId(website, username, password, logins._id));
console.log(this.state.website, username, password, logins._id);
};
render() {
const { editMode } = this.state;
const { logins } = this.props;
// const website = logins.website;
// const username = logins.username;
// const password = logins.password;
return (
<div className="card">
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("website", e.target.value)}
value={this.state.website}
/>
) : (
<p>{this.state.website}</p>
)}
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("username", e.target.value)}
value={this.state.username}
/>
) : (
<p>{logins.username}</p>
)}
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("password", e.target.value)}
value={this.state.password}
/>
) : (
<p>{logins.password}</p>
)}
{editMode ? (
<button onClick={this.handleSave}>save</button>
) : (
<button onClick={() => this.handleChange("editMode", true)}>
edit
</button>
)}
</div>
);
}
}
// this.handleChange("editMode", false)
function mapStateToProps(state) {
return {
// user: state.user.users,
// cards: state.cards.cards,
logins: state.logins.logins,
};
}
// App.propTypes = {
// user: PropTypes.array.isRequired,
// };
export default connect()(LoginIdCard);
"redux action file for editing the LoginId in mongodb"
export function editLoginId(website, username, password, id) {
return function (dispatch) {
const req = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
website: website,
username: username,
password: password,
cardId: id,
}),
};
fetch("http://localhost:9000/editLoginId", req)
.then((res) => res.json())
.then((data) =>
dispatch({
type: EDIT_LOGIN_ID,
payload: data,
})
)
.catch((err) => {
console.log(err);
<!-- begin snippet: js hide: false console: true babel: false -->
});
};
}

Can we allow the user to insert a drop-down option in addition to the already present drop-down in a react application?

While building a react application I want to enable a feature in the application to allow the user to add a new value in the drop-down. Is it possible to add this feature in the application?
Adding the following example
import React from "react";
import { MDBDropdown, MDBDropdownToggle, MDBDropdownMenu, MDBDropdownItem } from "mdbreact";
const DropdownPage = () => {
return (
<MDBDropdown>
<MDBDropdownToggle caret color="primary">
MDBDropdown
</MDBDropdownToggle>
<MDBDropdownMenu basic>
<MDBDropdownItem>Action</MDBDropdownItem>
<MDBDropdownItem>Another Action</MDBDropdownItem>
<MDBDropdownItem>Something else here</MDBDropdownItem>
<MDBDropdownItem divider />
<MDBDropdownItem>Separated link</MDBDropdownItem>
</MDBDropdownMenu>
</MDBDropdown>
);
}
export default DropdownPage;
After the divider can we add a '+' symbol and if the user clicks on it then he can type in a new value and that gets added to the existing dropdown. Can anyone please help with the following.
Regards
import React from 'react';
import { MDBDropdown, MDBDropdownToggle, MDBDropdownMenu, MDBDropdownItem } from "mdbreact";
export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
isDrop: false,
isNewItem: false,
items: ['Action', 'Another Action', 'Something else here'],
newItem: ''
}
}
render (){
return (
<MDBDropdown>
<MDBDropdownToggle caret color="primary">
MDBDropdown
</MDBDropdownToggle>
<MDBDropdownMenu>
{
this.state.items.map((item, index) => {
return(
<MDBDropdownItem key={index}>{item}</MDBDropdownItem>
)
})
}
<MDBDropdownItem divider />
<MDBDropdownItem>Separated link</MDBDropdownItem>
<MDBDropdownItem toggle={false} onClick={this.updateIsNewItem}>{this.state.isNewItem ? 'x' : '+'}</MDBDropdownItem>
{
this.state.isNewItem &&
<MDBDropdownItem toggle={false}>
<input name='newItem' type='text' onChange={this.handleChange} value={this.state.newItem}/>
<button onClick={this.addNewItem}>Add</button>
</MDBDropdownItem>
}
</MDBDropdownMenu>
</MDBDropdown>
)
}
addNewItem = () => {
if(this.state.newItem !== ''){
this.setState({
items: [...this.state.items, this.state.newItem],
newItem: '',
isNewItem: false
});
}
}
handleChange = (event) => {
const name = event.target.name;
const value = event.target.value;
this.setState({[name]: value});
}
updateIsNewItem = () => {
this.setState(prevState => ({isNewItem: !prevState.isNewItem}));
}
}

Form validation isn't working with mui react using mui-places-autocomplete

I'm using material ui react, redux-form, revalidate and mui-places-autocomplete to implement an autocomplete component using google places API. I've managed to implement the lookup of places, but I can't seem to be able to get error handling to work like with the rest of my inputs.
Also, whenever I type something into the field and select a place from the dropdown, clear the field and click out of it, it resets to the last value it had instead of leaving the field blank and showing the error.
I tried using react-places autocomplete and it works as expected, but it lacks the material design which I require, so that isn't really an option.
Since mui-places-autocomplete has several props, including textFieldProps there should be a way to leverage the error prop from the text field and use it, but I am not sure how to do that.
Here is my code in case it helps show what I'm trying to accomplish.
PlaceInput.jsx
import React, { Component } from 'react';
import MUIPlacesAutocomplete from 'mui-places-autocomplete';
import Script from 'react-load-script';
/* MUI Components */
import FormControl from '#material-ui/core/FormControl';
import FormHelperText from '#material-ui/core/FormHelperText';
class PlaceInput extends Component {
state = {
scriptLoaded: false
}
handleScriptLoad = () => {
this.setState({
scriptLoaded: true
})
}
render() {
const { onSuggestionSelected, createAutocompleteRequest, meta: { touched, error }, ...other } = this.props;
return (
<FormControl error={touched && !!error} margin="normal" fullWidth>
<Script
url="https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places"
onLoad={this.handleScriptLoad}
/>
{
this.state.scriptLoaded &&
<MUIPlacesAutocomplete
onSuggestionSelected={onSuggestionSelected}
createAutocompleteRequest={createAutocompleteRequest}
renderTarget={() => (<div />)}
textFieldProps={{ ...other }}
/>
}
{
touched &&
error &&
<FormHelperText style={{ color: '#f44336' }}>{error}</FormHelperText>
}
</FormControl>
)
}
}
export default PlaceInput;
EventForm.jsx
/*global google*/
import { reduxForm, Field } from 'redux-form';
import { composeValidators, combineValidators, isRequired, hasLengthGreaterThan } from 'revalidate';
import { geocodeBySuggestion } from 'mui-places-autocomplete';
/* Form Inputs */
import PlaceInput from '../../../app/common/form/PlaceInput';
const validate = combineValidators({
title: isRequired({ message: 'The event title is required' }),
category: isRequired({ message: 'Please provide a category' }),
description: composeValidators(
isRequired({ message: 'Please enter a description' }),
hasLengthGreaterThan(4)({ message: 'Description needs to be at least 5 characters' })
)(),
city: isRequired('City'),
venue: isRequired('Venue'),
date: isRequired('Date')
});
class EventForm extends Component {
state = {
cityLatLng: {},
venueLatLng: {},
scriptLoaded: false
}
createAutocompleteRequestForCities = (inputValue) => {
return {
input: inputValue,
types: ['(cities)']
}
}
createAutocompleteRequestForEstablishments = (inputValue) => {
return {
input: inputValue,
types: ['establishment'],
location: new google.maps.LatLng(this.state.cityLatLng),
radius: 1000
}
}
handleScriptLoad = () => {
this.setState({
scriptLoaded: true
})
}
render() {
const { classes, invalid, submitting, pristine } = this.props;
return (
<form onSubmit={this.props.handleSubmit(this.onFormSubmit)}>
<Field
fullWidth
onSuggestionSelected={this.onSuggestionSelectedCity}
createAutocompleteRequest={this.createAutocompleteRequestForCities}
name="city"
helperText="Required field"
component={PlaceInput}
label="Event City"
/>
{
this.state.scriptLoaded &&
<Field
fullWidth
onSuggestionSelected={this.onSuggestionSelectedVenue}
createAutocompleteRequest {this.createAutocompleteRequestForEstablishments}
name="venue"
helperText="Required field"
component={PlaceInput}
label="Event Venue"
/>
}
)
}
}
EventForm.propTypes = {
classes: PropTypes.object.isRequired,
};
const mapStateToProps = (state, ownProps) => {
const eventId = ownProps.match.params.id;
let event = {};
if (eventId && state.events.length > 0) {
event = state.events.filter(event => event.id === eventId)[0];
}
return {
initialValues: event
}
}
const actions = {
createEvent,
updateEvent,
deleteEvent
}
export default compose(
connect(mapStateToProps, actions),
reduxForm({ form: 'reduxForm', enableReinitialize: true, validate }),
withStyles(styles)
)(EventForm);
I don't have an answer but you forgot to delete your Google API Key, be careful

How to combine Material-UI's snackbar and input components in react?

I'm using Material-UI components to build my website. I have a header component with a search field which uses mui InputBase under the hood. When user enters empty input (that is they don't enter anything and just click enter) I want to display a mui Snackbar which will warn the user the no meaningful input was entered.
I can't get the combination of the two components to work together. In addition because search field state doesn't really change when user enters nothing it doesn't reload so if user repeatedly presses Enter the snackbar won't appear. I use this.forceUpdate(); but is there a more elegant way to implement such logic?
This is my code:
for the search input field:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputBase from '#material-ui/core/InputBase';
import { withStyles } from '#material-ui/core/styles';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { getAppInfo } from '../../actions/appActions.js';
import constants from '../../constants.js';
import { AppSearchBarInputStyles } from '../styles/Material-UI/muiStyles.js';
import AppNotFound from './AppNotFound.js';
class AppSearchBarInput extends Component {
state = {
appId: ''
}
onChange = e => {
this.setState({ appId: e.target.value });
}
onKeyDown = e => {
const { appId } = this.state;
if (e.keyCode === constants.ENTER_KEY && appId !== '') {
this.props.getAppInfo({ appId });
this.setState({
appId: ''
});
}
this.props.history.push('/app/appInfo');
this.forceUpdate();
}
render() {
const { classes } = this.props;
const { appId } = this.state;
console.log(`appId from AppSearchInput=${appId === ''}`);
return (
<div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
value={this.state.appId} />
{ appId === '' ? <AppNotFound message={constants.MESSAGES.APP_BLANK()}/> : ''}
</div>
)
}
}
AppSearchBarInput.propTypes = {
classes: PropTypes.object.isRequired
}
const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
const AppSearchBarWithStylesWithRouter = withRouter(AppSearchBarWithStyles);
export default connect(null, { getAppInfo })(AppSearchBarWithStylesWithRouter);
for the snackbar:
import React from 'react';
import Snackbar from '#material-ui/core/Snackbar';
import constants from '../../constants.js';
import SnackbarMessage from './SnackbarMessage.js';
class AppNotFound extends React.Component {
state = {
open: true,
};
handleClose = event => {
this.setState({ open: false });
};
render() {
const { message } = this.props;
return (
<Snackbar
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={this.state.open}
autoHideDuration={6000}
onClose={this.handleClose}
>
<SnackbarMessage
onClose={this.handleClose}
variant="warning"
message={message}
/>
</Snackbar>
);
}
}
export default AppNotFound;
I think the good way to achieve what You want is by adding another state property called snackBarOpen which will help You to determine if user has entered empty value or something meaningful:
AppSearchBarInput Component
state = {
appId: '',
snackBarOpen: false
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && e.target.value === '') {
this.setState({
appId: e.target.value,
snackBarOpen: true
});
} else {
this.setState({
appId: e.target.value
})
}
}
handleCloseSnackBar = () => {
this.setState({
snackBarOpen: false
});
}
And then just render also <AppNotFound /> in render() method(it will be hidden by default because it will depend on open prop):
render() {
const { snackBarOpen } = this.state;
return(
<div>
/* <InputBase /> here */
<AppNotFound message={/* Your message here */} open={snackBarOpen} onClose={this.handleCloseSnackBar} />
</div>
)
}
AppNotFound Component
You can remove all methods now and leave only render() one which will look next:
render() {
const { message, open, onClose } = this.props;
return (
<Snackbar
// ...
open={open}
// ...
onClose={onClose}
>
<SnackbarMessage
onClose={onClose}
// ...
message={message}
/>
</Snackbar>
);
}
Hope that my answer will be useful :)

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) {
...
}
...
}
...

Categories