-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/new context api #198
Changes from 7 commits
62a0bf0
46534c2
c3b97f7
3a773b6
8180300
547ef49
37bf258
474ddc2
c3790fe
ee08c30
0f45780
238e443
8f49cb2
f33066b
ddaaae0
10b3935
bfa8767
4d02570
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react'; | ||
import { FormsyContextInterface } from './interfaces'; | ||
|
||
const noFormsyErrorMessage = 'No Context Provider defined'; | ||
|
||
const throwNoFormsyProvider = () => { | ||
throw new Error(noFormsyErrorMessage); | ||
}; | ||
|
||
const defaultValue = { | ||
attachToForm: throwNoFormsyProvider, | ||
detachFromForm: throwNoFormsyProvider, | ||
isFormDisabled: true, | ||
isValidValue: throwNoFormsyProvider, | ||
validate: throwNoFormsyProvider, | ||
}; | ||
|
||
export default React.createContext<FormsyContextInterface>(defaultValue); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; | |
|
||
import utils from './utils'; | ||
import { Validations, WrappedComponentClass, RequiredValidation, Value } from './interfaces'; | ||
import FormsyContext from './FormsyContext'; | ||
|
||
/* eslint-disable react/default-props-match-prop-types */ | ||
|
||
|
@@ -102,19 +103,19 @@ function getDisplayName(component: WrappedComponentClass) { | |
); | ||
} | ||
|
||
export default function<Props, State, CompState>( | ||
export default function<Props, State>( | ||
WrappedComponent: React.ComponentClass<Props & State>, | ||
): React.ComponentClass<Props & State> { | ||
return class extends React.Component<Props & State & WrapperProps, WrapperState> { | ||
public static contextType = FormsyContext; | ||
|
||
public validations?: Validations; | ||
|
||
public requiredValidations?: Validations; | ||
|
||
public static displayName = `Formsy(${getDisplayName(WrappedComponent)})`; | ||
|
||
public static contextTypes = { | ||
formsy: PropTypes.object, // What about required? | ||
}; | ||
public context: React.ContextType<typeof FormsyContext>; | ||
|
||
public static defaultProps: any = { | ||
innerRef: null, | ||
|
@@ -143,7 +144,14 @@ export default function<Props, State, CompState>( | |
|
||
public componentDidMount() { | ||
const { validations, required, name } = this.props; | ||
const { formsy } = this.context; | ||
const { attachToForm } = this.context; | ||
|
||
const configure = () => { | ||
this.setValidations(validations, required); | ||
|
||
// Pass a function instead? | ||
attachToForm(this); | ||
}; | ||
|
||
if (!name) { | ||
throw new Error('Form Input requires a name property when used'); | ||
|
@@ -152,27 +160,23 @@ export default function<Props, State, CompState>( | |
this.setValidations(validations, required); | ||
|
||
// Pass a function instead? | ||
formsy.attachToForm(this); | ||
configure(); | ||
} | ||
|
||
public shouldComponentUpdate(nextProps, nextState, nextContext) { | ||
const { | ||
props, | ||
state, | ||
context: { formsy: formsyContext }, | ||
} = this; | ||
const { props, state, context } = this; | ||
const isPropsChanged = Object.keys(props).some(k => props[k] !== nextProps[k]); | ||
|
||
const isStateChanged = Object.keys(state).some(k => state[k] !== nextState[k]); | ||
|
||
const isFormsyContextChanged = Object.keys(formsyContext).some(k => formsyContext[k] !== nextContext.formsy[k]); | ||
const isFormsyContextChanged = Object.keys(context).some(k => context[k] !== nextContext[k]); | ||
|
||
return isPropsChanged || isStateChanged || isFormsyContextChanged; | ||
} | ||
|
||
public componentDidUpdate(prevProps) { | ||
const { value, validations, required } = this.props; | ||
const { formsy } = this.context; | ||
const { validate } = this.context; | ||
|
||
// If the value passed has changed, set it. If value is not passed it will | ||
// internally update, and this will never run | ||
|
@@ -183,15 +187,14 @@ export default function<Props, State, CompState>( | |
// If validations or required is changed, run a new validation | ||
if (!utils.isSame(validations, prevProps.validations) || !utils.isSame(required, prevProps.required)) { | ||
this.setValidations(validations, required); | ||
formsy.validate(this); | ||
validate(this); | ||
} | ||
} | ||
|
||
// Detach it when component unmounts | ||
// eslint-disable-next-line react/sort-comp | ||
public componentDidUnmount() { | ||
const { formsy } = this.context; | ||
formsy.detachFromForm(this); | ||
public componentWillUnmount() { | ||
const { detachFromForm } = this.context; | ||
detachFromForm(this); | ||
} | ||
|
||
public getErrorMessage = () => { | ||
|
@@ -220,9 +223,7 @@ export default function<Props, State, CompState>( | |
|
||
// By default, we validate after the value has been set. | ||
// A user can override this and pass a second parameter of `false` to skip validation. | ||
public setValue = (value, validate = true) => { | ||
const { formsy } = this.context; | ||
|
||
public setValue = (value: any, validate = true) => { | ||
if (!validate) { | ||
this.setState({ | ||
value, | ||
|
@@ -234,7 +235,7 @@ export default function<Props, State, CompState>( | |
isPristine: false, | ||
}, | ||
() => { | ||
formsy.validate(this); | ||
this.context.validate(this); //eslint-disable-line | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add the rule here? It would be nice to know what eslint is mad about. |
||
}, | ||
); | ||
} | ||
|
@@ -244,7 +245,7 @@ export default function<Props, State, CompState>( | |
public hasValue = () => this.state.value !== ''; | ||
|
||
// eslint-disable-next-line react/destructuring-assignment | ||
public isFormDisabled = () => this.context.formsy.isFormDisabled; | ||
public isFormDisabled = () => this.context.isFormDisabled; | ||
|
||
// eslint-disable-next-line react/destructuring-assignment | ||
public isFormSubmitted = () => this.state.formSubmitted; | ||
|
@@ -259,19 +260,19 @@ export default function<Props, State, CompState>( | |
public isValid = () => this.state.isValid; | ||
|
||
// eslint-disable-next-line react/destructuring-assignment | ||
public isValidValue = value => this.context.formsy.isValidValue.call(null, this, value); | ||
public isValidValue = value => this.context.isValidValue(this, value); | ||
|
||
public resetValue = () => { | ||
const { pristineValue } = this.state; | ||
const { formsy } = this.context; | ||
const { validate } = this.context; | ||
|
||
this.setState( | ||
{ | ||
value: pristineValue, | ||
isPristine: true, | ||
}, | ||
() => { | ||
formsy.validate(this); | ||
validate(this); | ||
}, | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import formDataToObject from 'form-data-to-object'; | ||
import FormsyContext from './FormsyContext'; | ||
|
||
import utils from './utils'; | ||
import validationRules from './validationRules'; | ||
|
@@ -14,6 +15,7 @@ import { | |
ISetInputValue, | ||
IUpdateInputsWithError, | ||
ValidationFunction, | ||
FormsyContextInterface, | ||
} from './interfaces'; | ||
|
||
type FormHTMLAttributesCleaned = Omit<React.FormHTMLAttributes<HTMLFormElement>, 'onChange' | 'onSubmit'>; | ||
|
@@ -55,6 +57,7 @@ export interface FormsyState { | |
isPristine?: boolean; | ||
isSubmitting: boolean; | ||
isValid: boolean; | ||
contextValue: FormsyContextInterface; | ||
} | ||
|
||
class Formsy extends React.Component<FormsyProps, FormsyState> { | ||
|
@@ -125,38 +128,31 @@ class Formsy extends React.Component<FormsyProps, FormsyState> { | |
validationErrors: PropTypes.object, // eslint-disable-line | ||
}; | ||
|
||
public static childContextTypes = { | ||
formsy: PropTypes.object, | ||
}; | ||
|
||
public constructor(props: FormsyProps) { | ||
super(props); | ||
this.state = { | ||
canChange: false, | ||
isSubmitting: false, | ||
isValid: true, | ||
contextValue: { | ||
attachToForm: this.attachToForm, | ||
detachFromForm: this.detachFromForm, | ||
isFormDisabled: props.disabled, | ||
isValidValue: (component, value) => this.runValidation(component, value).isValid, | ||
validate: this.validate, | ||
}, | ||
}; | ||
this.inputs = []; | ||
this.emptyArray = []; | ||
} | ||
|
||
public getChildContext = () => ({ | ||
formsy: { | ||
attachToForm: this.attachToForm, | ||
detachFromForm: this.detachFromForm, | ||
isFormDisabled: this.isFormDisabled(), | ||
isValidValue: this.isValidValue, | ||
validate: this.validate, | ||
}, | ||
}); | ||
|
||
public componentDidMount = () => { | ||
this.prevInputNames = this.inputs.map(component => component.props.name); | ||
this.validateForm(); | ||
}; | ||
|
||
public componentDidUpdate = () => { | ||
const { validationErrors } = this.props; | ||
public componentDidUpdate = (prevProps: FormsyProps) => { | ||
const { validationErrors, disabled } = this.props; | ||
|
||
if (validationErrors && typeof validationErrors === 'object' && Object.keys(validationErrors).length > 0) { | ||
this.setInputValidationErrors(validationErrors); | ||
|
@@ -167,6 +163,17 @@ class Formsy extends React.Component<FormsyProps, FormsyState> { | |
this.prevInputNames = newInputNames; | ||
this.validateForm(); | ||
} | ||
|
||
if (disabled !== prevProps.disabled) { | ||
// eslint-disable-next-line | ||
this.setState(state => ({ | ||
...state, | ||
contextValue: { | ||
...state.contextValue, | ||
isFormDisabled: disabled, | ||
}, | ||
})); | ||
} | ||
}; | ||
|
||
public getCurrentValues = () => | ||
|
@@ -237,8 +244,6 @@ class Formsy extends React.Component<FormsyProps, FormsyState> { | |
} | ||
}; | ||
|
||
public isValidValue = (component, value) => this.runValidation(component, value).isValid; | ||
|
||
// eslint-disable-next-line react/destructuring-assignment | ||
public isFormDisabled = () => this.props.disabled; | ||
|
||
|
@@ -485,9 +490,7 @@ class Formsy extends React.Component<FormsyProps, FormsyState> { | |
// If there are no inputs, set state where form is ready to trigger | ||
// change event. New inputs might be added later | ||
if (!this.inputs.length) { | ||
this.setState({ | ||
canChange: true, | ||
}); | ||
onValidationComplete(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, and i also fixed a bug, where if you remove all the inputs but the last one was invalid, the form was still invalid. The callback onValidationComplete was never called. |
||
} | ||
}; | ||
|
||
|
@@ -519,20 +522,19 @@ class Formsy extends React.Component<FormsyProps, FormsyState> { | |
showError, | ||
showRequired, | ||
validationErrors, | ||
children, | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ | ||
...nonFormsyProps | ||
} = this.props; | ||
|
||
return React.createElement( | ||
'form', | ||
{ | ||
onReset: this.resetInternal, | ||
onSubmit: this.submit, | ||
...nonFormsyProps, | ||
disabled: false, | ||
}, | ||
// eslint-disable-next-line react/destructuring-assignment | ||
this.props.children, | ||
const { contextValue } = this.state; | ||
|
||
return ( | ||
// eslint-disable-next-line | ||
<FormsyContext.Provider value={contextValue}> | ||
<form onReset={this.resetInternal} onSubmit={this.submit} {...nonFormsyProps}> | ||
{children} | ||
</form> | ||
</FormsyContext.Provider> | ||
); | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's been 2 years, so this seems like an acceptable change.