Skip to content

Instantly share code, notes, and snippets.

@jordangarcia
Created August 1, 2018 23:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jordangarcia/861fde0820bcb5f0e0c81bf6ce234103 to your computer and use it in GitHub Desktop.
Save jordangarcia/861fde0820bcb5f0e0c81bf6ce234103 to your computer and use it in GitHub Desktop.
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