Created
August 1, 2018 23:21
-
-
Save jordangarcia/861fde0820bcb5f0e0c81bf6ce234103 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import ui from 'core/ui'; | |
import PropTypes from 'prop-types'; | |
import { Immutable } from 'nuclear-js'; | |
import { connect } from 'core/ui/decorators'; | |
import { Input as OuiInput, Button } from 'optimizely-oui' | |
import SectionModule from '../../section_module/'; | |
class Input extends React.Component { | |
onChange = (ev) => { | |
this.props.onChange(ev.target.value) | |
} | |
render() { | |
let displayError = false | |
if (this.props.errors) { | |
displayError = true; | |
} | |
return ( | |
<OuiInput {...this.props} onChange={this.onChange} displayError={ displayError }/> | |
) | |
} | |
} | |
/** | |
* example HoC | |
connect = function(mapStateToProps) { | |
return function(ReactComponentClass) { | |
return ui.connectGettersI(ReactComponentClass, mapStateToProps) | |
} | |
} | |
*/ | |
function WrapInput({ | |
validateFn, | |
keypath, | |
}) { | |
return function(InputComponent) { | |
return class WrappedFormInput extends React.Component { | |
// TODO(jordan): handle passing ref | |
validate = () => { | |
const { | |
form, | |
} = this.props; | |
form.updateErrors(keypath, validateFn); | |
} | |
render() { | |
const { | |
form, | |
} = this.props; | |
/** | |
* propTypes | |
* - value | |
* - errors | |
* - onChange | |
* form | |
* - getErrors(key) | |
* - updateIsValid | |
*/ | |
return ( | |
<InputComponent | |
value={ form.getValue(keypath) } | |
onChange={ form.update.bind(form, keypath) } | |
errors={ form.getErrors(keypath) } | |
/> | |
) | |
} | |
} | |
} | |
} | |
function Form({ | |
validateOnChange = true, | |
mapPropsToCleanStateGetter, | |
updateFn, | |
getValueFn, | |
}) { | |
return function(Component) { | |
class FormComponent extends React.Component { | |
constructor(props) { | |
// TODO(jordan): handle passing ref | |
super(props); | |
this.state = { | |
editingState: this.props.cleanState, | |
isDirty: false, | |
errors: {}, | |
} | |
this.savedRefs = [] | |
this.formActions = { | |
getValue: this.getValue, | |
update: this.update, | |
getErrors: this.getErrors, | |
addRef: this.addRef, | |
updateErrors: this.updateErrors, | |
revert: this.revert, | |
}; | |
} | |
revert = () => { | |
this.setState({ | |
editingState: this.props.cleanState, | |
}, () => { | |
this.validateAll(); | |
this.updateIsDirty(this.state.editingState, this.props.cleanState); | |
}); | |
} | |
updateErrors = (keypath, validateFn) => { | |
this.setState((prevState) => { | |
// clone deep? | |
const errors = prevState.errors; | |
errors[keypath] = validateFn(this.pureGetValue(prevState.editingState, keypath)); | |
return { | |
errors, | |
} | |
}); | |
} | |
addRef = (comp) => { | |
this.savedRefs.push(comp); | |
} | |
getValue = (keypath) => { | |
console.log('getting value', this.state.editingState.toJS(), keypath) | |
return this.pureGetValue(this.state.editingState, keypath); | |
} | |
pureGetValue = (editingState, keypath) => { | |
if (getValueFn) { | |
return getValue(editingState, keypath); | |
} | |
return editingState.get(keypath); | |
} | |
getErrors = (keypath) => { | |
return this.state.errors[keypath] || null; | |
} | |
validateAll = () => { | |
this.savedRefs.forEach(ref => { | |
if (ref && ref.validate) { | |
ref.validate(); | |
} | |
}) | |
} | |
updateIsDirty = (editingState, cleanState) => { | |
// TODO(jordan): add comparator fn | |
console.log('dirty check', isDirty, editingState.toJS(), cleanState.toJS()) | |
const isDirty = !Immutable.is(editingState, cleanState); | |
this.setState({ | |
isDirty, | |
}) | |
} | |
update = (keypath, value) => { | |
this.setState((prevState, props) => { | |
const editingState = (updateFn) | |
? updateFn(prevState.editingState, keypath, value) | |
: prevState.editingState.set(keypath, value); | |
this.updateIsDirty(editingState, props.cleanState); | |
return { | |
editingState, | |
}; | |
}, () => { | |
if (validateOnChange) { | |
this.validateAll(); | |
} | |
}); | |
// todo update dirty here | |
} | |
render() { | |
const { | |
form, | |
} = this.props; | |
return ( | |
<Component | |
{ ...this.props } | |
formActions={ this.formActions } | |
formState={ this.state } | |
/> | |
) | |
} | |
} | |
return ui.connectGetters( | |
FormComponent, | |
(props) => { | |
const cleanState = mapPropsToCleanStateGetter(props); | |
return { | |
cleanState, | |
} | |
} | |
) | |
} | |
} | |
class FormInputWrapper extends React.Component { | |
validate = () => { | |
if (!this.props.validateFn) { | |
return | |
} | |
const { | |
keypath, | |
form, | |
} = this.props; | |
form.updateErrors(keypath, this.props.validateFn); | |
} | |
render() { | |
const { | |
keypath, | |
form, | |
} = this.props; | |
/** | |
* propTypes | |
* - value | |
* - errors | |
* - onChange | |
* form | |
* - getErrors(key) | |
* - updateIsValid | |
*/ | |
return ( | |
React.cloneElement(this.props.input, { | |
value: form.getValue(keypath), | |
onChange: form.update.bind(form, keypath), | |
errors: form.getErrors(keypath), | |
}) | |
) | |
} | |
} | |
@WrapInput({ | |
keypath: 'name', | |
validateFn: (value) => { | |
if (value !== 'sorting_test') { | |
return 'shizzle!' | |
} | |
return null; | |
} | |
}) | |
class SimpleInput extends React.Component { | |
onChange = (ev) => { | |
this.props.onChange(ev.target.value) | |
} | |
render() { | |
/** | |
* propTypes | |
* - value | |
* - errors | |
* - onChange | |
* form | |
* - getErrors(key) | |
* - updateIsValid | |
*/ | |
let errors = null; | |
if (this.props.errors) { | |
errors = <p>Error: {this.props.errors}</p> | |
} | |
return ( | |
<div> | |
{ errors } | |
<input type="text" value={ this.props.value } onChange={ this.onChange} /> | |
</div> | |
) | |
} | |
} | |
@Form({ | |
mapPropsToCleanStateGetter: () => SectionModule.getters.currentlyEditingExperiment | |
}) | |
export default class MyForm extends React.Component { | |
constructor(props) { | |
super(props); | |
} | |
render() { | |
return ( | |
<form> | |
<p> | |
is dirty: <strong> { this.props.formState.isDirty ? 'true': 'false' } </strong> | |
</p> | |
<FormInputWrapper | |
ref={ (c) => { this.props.formActions.addRef(c) } } | |
form={ this.props.formActions } | |
keypath='name' | |
validateFn={ (val) => { | |
if (val !== 'sorting_test') { | |
return true; | |
//return 'fuck' | |
} | |
return null; | |
}} | |
input={ | |
<Input label='Experiment Name' /> | |
} | |
/> | |
<div> | |
<Button isDisabled={ !this.props.formState.isDirty } onClick={ this.props.formActions.revert }>Revert</Button> | |
</div> | |
</form> | |
) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment