I'm trying to create an endEditing function for reactJs,but i have problem
when using an event hooks it calls the event a focusout but it calls the functions declared in all inputs and not only in the input that was selected.
I tried to do this that activates the function of all inputs if I leave only one.
src/components.js
export default function Input({
inputRef,
inputType,
placeholder,
functionOnEndingChange,
functionUpdatedValueRef,
}) {
useEventListener('focusout', functionOnEndingChange);
return (
<InputText
type={inputType}
ref={inputRef}
placeholder={placeholder}
onChange={text => functionUpdatedValueRef(text.target.value)}
/>
);
}
Input.propTypes = {
functionUpdatedValueRef: PropTypes.func,
functionOnEndingChange: PropTypes.func,
inputType: PropTypes.string,
inputRef: PropTypes.func,
placeholder: PropTypes.string,
};
Input.defaultProps = {
functionUpdatedValueRef: () => {},
functionOnEndingChange: () => null,
inputType: 'text',
inputRef: () => {},
placeholder: 'placeholder input:',
};
src/utils/hooksEvent.js
export default function useEventListener(eventName, handler, element = window) {
// Create a ref that stores handler
const savedHandler = useRef();
// Update ref.current value if handler changes.
// This allows our effect below to always get latest handler ...
// ... without us needing to pass it in effect deps array ...
// ... and potentially cause effect to re-run every render.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(
() => {
// Make sure element supports addEventListener
// On
const isSupported = element && element.addEventListener;
if (!isSupported) return;
// Create event listener that calls handler function stored in ref
const eventListener = event => savedHandler.current(event);
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
},
[eventName, element] // Re-run if eventName or element changes
);
}
src/page/testeInput.js
<Column flex={1}>
<AreaInput>
<LabelText name="EMAIL" />
<Input
inputRef={inputEmailRef}
inputType="email"
placeholder="example#example.com"
functionUpdatedValueRef={text => setEmailState(text)}
functionOnEndingChange={() =>
verifyEmail('email', emailState)
}
/>
</AreaInput>
<AreaInput>
<LabelText name="EMAIL2" />
<Input
inputRef={inputEmailRef2}
inputType="email2"
placeholder="example#example.com"
functionUpdatedValueRef={text => setEmailState(text)}
functionOnEndingChange={() =>
verifyEmailTwo('email2', emailState)
}
/>
</AreaInput>
</Column>
by clicking on one of the inputs and leaving the focus it activates both functions
The answer is simple to the problem using onBlur solves the problem
https://reactjs.org/docs/events.html#focus-events
then the component can look like this
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import {
AreaInputIcon,
AreaInput,
Input,
InputFormMask,
AreaIcon,
IconUser,
IconOpenEyes,
IconClosedEyes,
} from './styles';
import { useEventListener } from '../../utils';
export default function InputIcon({
iconName,
button,
functionOnClick,
error,
disabled,
inputRef,
type,
functionUpdatedValueRef,
functionOnEndingChange,
placeholder,
inputMask,
}) {
// useEventListener('focusout', functionOnEndingChange);
function choseIcons(nameParam) {
switch (nameParam) {
case 'user':
return <IconUser />;
case 'passwordOpen':
return <IconOpenEyes />;
case 'passwordClosed':
return <IconClosedEyes />;
default:
return <IconUser />;
}
}
return (
<AreaInputIcon>
<AreaInput error={error}>
{type !== 'mask' ? (
<Input
ref={inputRef}
placeholder={placeholder}
onChange={text => functionUpdatedValueRef(text.target.value)}
onBlur={functionOnEndingChange}
/>
) : (
<InputFormMask
ref={inputRef}
mask={inputMask}
placeholder={placeholder}
onChange={text => functionUpdatedValueRef(text.target.value)}
onBlur={functionOnEndingChange}
/>
)}
</AreaInput>
<AreaIcon
button={button}
onClick={functionOnClick}
error={error}
disabled={disabled}
>
{choseIcons(iconName)}
</AreaIcon>
</AreaInputIcon>
);
}
InputIcon.propTypes = {
iconName: PropTypes.string,
button: PropTypes.bool,
functionOnClick: PropTypes.func,
functionUpdatedValueRef: PropTypes.func,
error: PropTypes.bool,
type: PropTypes.string,
disabled: PropTypes.bool,
inputRef: PropTypes.func,
functionOnEndingChange: PropTypes.func,
inputMask: PropTypes.string,
placeholder: PropTypes.string,
};
InputIcon.defaultProps = {
iconName: 'user',
button: false,
functionOnClick: () => {},
functionUpdatedValueRef: () => {},
error: false,
type: 'common',
disabled: true,
inputRef: () => {},
functionOnEndingChange: () => {},
inputMask: '99/99/99',
placeholder: 'palceholder input:',
};
Related
I created some logic that is reusable code anywhere. I have an input.jsx component for handling input elements, and use-form.js custom hook for managing all of the stuff and I have a basic-form component for creating form logic. Inside of basic-form component, I'm calling the custom hook many times. That is tedious and quite repetitive* so I wanna create two reusable invoking custom hooks in the input.jsx component
input.component.jsx
const Input = (props) => {
return (
<div className={`form-control ${props.error && 'invalid'}`}>
<label htmlFor={props.id}>{props.label}</label>
<input
type={props.type}
id={props.id}
value={props.value}
onChange={props.changeHandler}
onBlur={props.blurHandler}
/>
{props.error && <p className="error-text">{props.label} must be entered!</p>}
</div>
);
};
use-input.js
import { useReducer } from 'react';
const inputReducer = (state, action) => {
switch (action.type) {
case 'INPUT':
return { value: action.value, isTouched: state.isTouched };
case 'BLUR':
return { isTouched: true, value: state.value };
case 'RESET':
return { value: '', isTouched: false };
}
return state;
};
export const useInput = (validateValue) => {
const [state, dispatch] = useReducer(inputReducer, {
value: '',
isTouched: false,
});
const valueIsValid = validateValue(state.value);
const error = !valueIsValid && state.isTouched;
const valueChangeHandler = (e) => {
dispatch({ type: 'INPUT', value: e.target.value });
};
const inputBlurHandler = () => {
dispatch({ type: 'BLUR' });
};
const reset = () => {
dispatch({ type: 'RESET' });
};
return {
value: state.value,
isValid: valueIsValid,
error,
valueChangeHandler,
inputBlurHandler,
reset,
};
};
basic-form.component.jsx
import { useInput } from '../hooks/use-input';
import Input from './input.component';
const BasicForm = () => {
const {
value: name,
isValid: nameIsValid,
error: nameHasError,
valueChangeHandler: nameChangeHandler,
inputBlurHandler: nameBlurHandler,
reset: nameReset,
} = useInput((value) => value.trim() !== '');
const {
value: surname,
isValid: surnameIsValid,
error: surnameHasError,
valueChangeHandler: surnameChangeHandler,
inputBlurHandler: surnameBlurHandler,
reset: surnameReset,
} = useInput((value) => value.trim() !== '');
const {
value: email,
isValid: emailIsValid,
error: emailHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: emailReset,
} = useInput((value) => /^\S+#\S+\.\S+$/.test(value));
let formIsValid = false;
if (nameIsValid && surnameIsValid && emailIsValid) {
formIsValid = true;
}
const formSubmitHandler = (e) => {
e.preventDefault();
nameReset();
surnameReset();
emailReset();
};
return (
<form onSubmit={formSubmitHandler}>
<div className="control-group">
<Input
id="name"
label="First Name"
type="text"
error={nameHasError}
value={name}
changeHandler={nameChangeHandler}
blurHandler={nameBlurHandler}
/>
<Input
id="surname"
label="Last Name"
type="text"
error={surnameHasError}
value={surname}
changeHandler={surnameChangeHandler}
blurHandler={surnameBlurHandler}
/>
<Input
id="email"
label="Email"
type="email"
error={emailHasError}
value={email}
changeHandler={emailChangeHandler}
blurHandler={emailBlurHandler}
/>
</div>
<div className="form-actions">
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default BasicForm;
The problem with that is we must return two values from input.jsx to basic-form.jsx. The first value shows the property isValid or not. Second, a function for reseting every individual input element. Also, we must receive a function that validates the logic from the parent to the children components. How can we solve this problem? I think we should use useRef, useImperativeHandle and forwardRef but how?
Please vote up for more people to see.
I have a form that uses a child component with input fields. I have used props to get the data from the parent component, but it's not adding the prop value to the input field. There is no errors in the console.
Parent component:
const {
address,
errors
} = this.state;
return (
<form noValidate autoComplete="off" onSubmit={this.onSubmit.bind(this)}>
<LocationInputGroup
errors={errors}
address={address}
/>
<Button
type="submit"
style={{ marginRight: 10, marginTop: 20 }}
variant="callToAction"
> Submit</Button>
</form>
);
Child component:
constructor(props) {
super(props);
this.state = {
address: "",
errors: {}
};
}
componentDidMount() {
this.setState({
errors: this.props.errors,
address: this.props.address
});
}
render() {
const {
address,
errors
} = this.state;
return (
<div>
<InputGroup
value={address}
error={errors.address}
label="Address"
name={"address"}
onChange={e => this.setState({ address: e.target.value })}
placeholder={"Address"}
/>
</div>
);
}
InputGroup component:
class InputGroup extends Component {
constructor(props) {
super(props);
}
//TODO check if scrolling still changes with number inputs
//Bug was in Chrome 73 https://www.chromestatus.com/features/6662647093133312
//If it's no longer a bug these listeners can be removed and the component changed back to a stateless component
handleWheel = e => e.preventDefault();
componentDidMount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).addEventListener("wheel", this.handleWheel);
}
}
componentWillUnmount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).removeEventListener("wheel", this.handleWheel);
}
}
render() {
const {
disabled,
classes,
error,
value,
name,
label,
placeholder,
type,
isSearch,
onChange,
onBlur,
onFocus,
multiline,
autoFocus,
InputProps = {},
autoComplete,
allowNegative,
labelProps
} = this.props;
if (type === "phone") {
InputProps.inputComponent = PhoneNumberInputMask;
}
let onChangeEvent = onChange;
//Stop them from entering negative numbers unless they explicitly allow them
if (type === "number" && !allowNegative) {
onChangeEvent = e => {
const numberString = e.target.value;
if (!isNaN(numberString) && Number(numberString) >= 0) {
onChange(e);
}
};
}
return (
<FormControl
className={classes.formControl}
error
aria-describedby={`%${name}-error-text`}
>
<FormatInputLabel {...labelProps}>{label}</FormatInputLabel>
<TextField
error={!!error}
id={name}
type={type}
value={value}
onChange={onChangeEvent}
margin="normal"
onBlur={onBlur}
onFocus={onFocus}
InputProps={{
...InputProps,
classes: {
input: classnames({
[classes.input]: true,
[classes.searchInput]: isSearch
})
}
}}
placeholder={placeholder}
multiline={multiline}
autoFocus={autoFocus}
disabled={disabled}
autoComplete={autoComplete}
onWheel={e => e.preventDefault()}
/>
<FormHelperText
className={classes.errorHelperText}
id={`${name}-error-text`}
>
{error}
</FormHelperText>
</FormControl>
);
}
}
InputGroup.defaultProps = {
value: "",
type: "text",
labelProps: {}
};
InputGroup.propTypes = {
error: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string.isRequired,
label: PropTypes.string,
placeholder: PropTypes.string,
type: PropTypes.string,
isSearch: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
multiline: PropTypes.bool,
autoFocus: PropTypes.bool,
InputProps: PropTypes.object,
disabled: PropTypes.bool,
autoComplete: PropTypes.string,
allowNegative: PropTypes.bool,
labelProps: PropTypes.object
};
export default withStyles(styles)(InputGroup);
I hope someone can advise what the issue is and how to overcome it.
Normally when we pass data from parent, we consider it to be Immutable,
(i.e) It should not be changed directly from the child components.
So, what one could do is use the prop value directly from the parent and use a method to mutate or change from the parent.
Below code is self explanatory.
Parent
class Parent {
// parent immutable state
public parentObject: object = {
location: "",
phoneNo: ""
};
constructor() {
this.state = {
parentObject: this.parentObject
}
}
public onParentObjectChangeCallBack(key: string, value: string | number) {
this.state.parentObject[key] = value;
// to rerender
this.setState();
}
public render () {
return <ChildComponent parentObject={this.state.parentObject}
onChange={this.onParentObjectChangeCallBack} />
}
}
Child
class ChildComponent {
#Prop
public parentObject: object;
#Prop
public onChange: Function;
public render() {
<div>
{
this.parentObject.keys.map((key) => {
return <div>
<span> {{key}}</span>
// calls parent method to change the immutable object
<input value={{this.parentObject[key]}} onChange=(val) =>
this.onChange(key, val) />
</div>
})
}
</div>
}
}
I have this parent component:
filtersModal.js
// imports
class FiltersModal extends React.Component {
state = { carrier: '' };
applyFilters = () => {
const { carrier } = this.state;
applyFilters({ carrier });
};
handleChange = field => ev => {
this.setState({ [field]: ev.target.value });
};
render() {
return (
<Modal
open={isFiltersModalOpened}
onRequestSubmit={this.applyFilters}
>
<Form>
<GetAllCouriers handleCouriers={this.handleChange('carrier')} />
</Form>
</Modal>
);
}
}
FiltersModal.propTypes = {
t: PropTypes.func.isRequired,
isFiltersModalOpened: PropTypes.bool.isRequired,
toggleFiltersModal: PropTypes.func.isRequired,
applyFilters: PropTypes.func.isRequired,
};
export default translate()(FiltersModal);
And also I have this other component which is a child of the above one:
getAllCouriers.js
// other imports
import CouriersSelect from '../CouriersSelect';
const GetAllCouriers = ({ t, softlayerAccountId, handleCouriers }) => (
<Query query={GET_ALL_COURIERS}>
{({ loading, error, data }) => {
let getAllCouriers;
if (data && data.GetAllCouriers) {
getAllCouriers = data.GetAllCouriers;
}
return (
<CouriersSelect
allCouriersList={getAllCouriers}
onChange={handleCouriers}
/>
);
}}
</Query>
);
GetAllCouriers.propTypes = {
t: PropTypes.func.isRequired,
softlayerAccountId: PropTypes.string.isRequired,
};
export default compose(
connect(store => ({
softlayerAccountId: store.global.softlayerAccountId,
})),
translate(),
)(GetAllCouriers);
And this is the component which is a child of the right above one that contains the onChange function handling the prop handleCouriers
couriersSelect.js
// imports
const CouriersSelect = ({ t, handleCouriers, allCouriersList }) => (
<Select onChange={handleCouriers}>
{allCouriersList.map(carriers => (
<SelectItem
key={carriers.name}
value={carriers.name}
text={carriers.name}
/>
))}
</Select>
);
CouriersSelect.propTypes = {
t: PropTypes.func.isRequired,
allCouriersList: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
handleCouriers: PropTypes.func.isRequired,
};
export default translate()(CouriersSelect);
I have some different components where I need to pass the handleChange function on FiltersModal.js
handleChange = field => ev => {
this.setState({ [field]: ev.target.value });
};
One of these components is getAllCouriers.js, I need to make it to work there. As you see, getAllCouriers.js is called on filtersModal.js but the onChange function is present on couriersSelect.js. So this function has to travel from couriersSelect.js which is a child of getAllCouriers.js and getAllCouriers.js is a child of filtersModal.js.
What can I do?
Keep you function in top most component and Pass function like this:
<GetAllCouriers handleCouriers={() => this.handleChange('carrier')} />
Hope this answered your questions
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) {
...
}
...
}
...
I have a container component where I have few functions, like this two for example:
newPeriodeCallback() {
const {
behandlingFormPrefix, perioder, periodeTyper, utsettelseArsaker,
} = this.props;
const {
uttakNyPeriodeFom, uttakNyPeriodeTom, uttakNyPeriodeType, uttakNyPeriodeAndel, uttakNyPeriodeArsak,
} = this.props.nyPeriode;
const getPeriodeData = (periode, periodeArray) => periodeArray
.filter(({ kode }) => kode === periode);
const periodeObjekt = getPeriodeData(uttakNyPeriodeType, periodeTyper)[0];
const arsakObjekt = getPeriodeData(uttakNyPeriodeArsak, utsettelseArsaker)[0];
const utsettelseĆ
rsak = arsakObjekt !== undefined ? {
kode: arsakObjekt.kode,
kodeverk: arsakObjekt.kodeverk,
navn: arsakObjekt.navn,
} : {};
const nyPeriode = [{
arbeidstidsprosent: uttakNyPeriodeAndel,
bekreftet: false,
fom: uttakNyPeriodeFom,
saksebehandlersBegrunnelse: null,
tom: uttakNyPeriodeTom,
utsettelseĆ
rsak,
uttakPeriodeType: {
kode: periodeObjekt.kode,
kodeverk: periodeObjekt.kodeverk,
navn: periodeObjekt.navn,
},
}];
this.props.reduxFormChange(`${behandlingFormPrefix}.UttakInfoPanel`, 'perioder', perioder.concat(nyPeriode)
.sort((a, b) => a.fom > b.fom));
this.setState({ isNyPeriodeFormOpen: !this.state.isNyPeriodeFormOpen });
}
removePeriodCallback(index) {
const { behandlingFormPrefix, perioder } = this.props;
this.props.reduxFormChange(
`${behandlingFormPrefix}.UttakInfoPanel`, 'perioder',
perioder.filter((e, i) => i === index)
.sort((a, b) => a.fom > b.fom),
);
}
I am sending these two functions as callbacks to different components:
<FieldArray
name="perioder"
component={UttakPeriode}
removePeriodCallback={this.removePeriodCallback}
inntektsmelding={inntektsmelding}
/>
<UttakNyPeriode newPeriodeCallback={this.newPeriodeCallback} />
The problem I have is when I am clicking on a button in the component where I am sending the newPeriodeCallback:
<Hovedknapp
className={styles.oppdaterMargin}
htmlType="button"
mini
onClick={newPeriodeCallback}
>
In the container component on inspecting the console I see that the removePeriodCallback is also being triggered which then removes the new period that is being added in the function newPeriodeCallback. Why is this happening, and how can I fix this?
Update
I have tried by following the suggestion in the comment, to use the arrow function in onClick, like this:
<Image
className={styles.removeIcon}
src={removePeriod}
onClick={() => removePeriodCallback(index)}
alt="Slett periode"
/>
And that has stopped the function from triggering from other places, but it is not triggering onClick either. What is wrong with this code?
This is the complete child component:
export const UttakPeriodeType = ({
bekreftet,
tilDato,
fraDato,
uttakPeriodeType,
removePeriodCallback,
index,
}) => (
<div className={classNames('periodeType', { active: !bekreftet })}>
<div className={styles.headerWrapper}>
<Element>{uttakPeriodeType.navn}</Element>
<div>
<Image src={editPeriod} alt="Rediger periode" />
<Image
className={styles.removeIcon}
src={removePeriod}
onClick={() => removePeriodCallback(index)}
alt="Slett periode"
/>
</div>
</div>
<div>
And this is the image component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from 'react-intl';
import Tooltip from 'sharedComponents/Tooltip';
export class Image extends Component {
constructor() {
super();
this.state = {
isHovering: false,
};
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
onFocus() {
this.setState({ isHovering: true });
}
onBlur() {
this.setState({ isHovering: false });
}
onKeyDown(e) {
if (e.key === 'Enter' || e.key === ' ') {
this.props.onKeyDown(e);
e.preventDefault();
}
}
render() {
const image = (<img // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
className={this.props.className}
src={this.props.src !== null ? this.props.src : this.props.imageSrcFunction(this.state.isHovering)}
alt={this.props.altCode ? this.props.intl.formatMessage({ id: this.props.altCode }) : this.props.alt}
title={this.props.titleCode ? this.props.intl.formatMessage({ id: this.props.titleCode }) : this.props.title}
tabIndex={this.props.tabIndex}
onMouseOver={this.onFocus}
onMouseOut={this.onBlur}
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onMouseDown={this.props.onMouseDown}
/>);
if (this.props.tooltip === null) {
return image;
}
return (
<Tooltip header={this.props.tooltip.header} body={this.props.tooltip.body}>
{image}
</Tooltip>
);
}
}
Image.propTypes = {
className: PropTypes.string,
src: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape(),
]),
imageSrcFunction: PropTypes.func,
onMouseDown: PropTypes.func,
onKeyDown: PropTypes.func,
alt: PropTypes.string,
altCode: PropTypes.string,
title: PropTypes.string,
titleCode: PropTypes.string,
tabIndex: PropTypes.string,
tooltip: PropTypes.shape({
header: PropTypes.string.isRequired,
body: PropTypes.string,
}),
intl: intlShape.isRequired,
};
Image.defaultProps = {
className: '',
src: null,
imageSrcFunction: null,
onMouseDown: null,
onKeyDown: null,
tabIndex: null,
tooltip: null,
alt: null,
altCode: null,
title: null,
titleCode: null,
};
export default injectIntl(Image);