Skip to content

Commit

Permalink
Refactors with flow and helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
alanzanattadev committed Apr 10, 2017
1 parent 9cde91b commit 4410d6c
Show file tree
Hide file tree
Showing 18 changed files with 902 additions and 468 deletions.
Empty file added .flowconfig
Empty file.
804 changes: 490 additions & 314 deletions dist/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/bundle.js.map

Large diffs are not rendered by default.

118 changes: 74 additions & 44 deletions lib/FormController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,44 @@
import React from "react";
import { fromJS, is } from "immutable";
import Rx from "rxjs";
import { setValue } from "./StateValue";

function createValueChangeEvent(newValue, statePath) {
return {
type: VALUE_CHANGE_EVENT_TYPE,
newValue,
statePath,
};
}

function createInitialChangeEvent(newInitial) {
return {
type: INITIAL_CHANGE_EVENT_TYPE,
newValue: newInitial,
};
}

function createSubmitChangeEvent() {
return {
type: SUBMIT_EVENT_TYPE,
};
}

export const VALUE_CHANGE_EVENT_TYPE = "VALUE_CHANGE";
export const INITIAL_CHANGE_EVENT_TYPE = "INITIAL_CHANGE";
export const SUBMIT_EVENT_TYPE = "SUBMIT";
import {
INITIAL_CHANGE_EVENT_TYPE,
VALIDATION_FAILED_EVENT_TYPE,
VALUE_CHANGE_EVENT_TYPE,
SUBMIT_EVENT_TYPE,
createInitialChangeEvent,
createSubmitChangeEvent,
createValidationFailedEvent,
createValueChangeEvent,
} from "./FormEvents";
import { setValue } from "./StateValueHelpers";
import {
Validation,
type ValidationError,
type ValidationInfo,
} from "./ValidationHelpers";

export default function FormController(
applyControl = state => state,
convertIn = (value, props) => value,
convertOut = (value, props) => value,
applyControl: (state: any) => any = state => state,
convertIn: (value: any, props: any) => Object = (value, props) => value,
convertOut: (value: any, props: any) => Object = (value, props) => value,
{
checkIfModified = true,
immutableInitial = false,
}: {
checkIfModified: boolean,
immutableInitial: boolean,
} = {},
): () => React.Component<any, any, any> {
validate: (value: any, props: any) => ValidationInfo = (value, props) => true,
): (Comp: React$Component<any, any, any>) => React$Component<any, any, any> {
return function(
WrappedComponent: React.Component<any, any, any>,
WrappedComponent: React$Component<any, any, any>,
): React.Component<any, any, any> {
class Controller extends React.Component {
class Controller extends React.Component<any, any, any> {
subject: Rx.Subject;
valueChangeObs: Rx.Subject;
rootDispatcherGetter: ?() => any;

constructor(props) {
super();
this.subject = new Rx.Subject()
Expand All @@ -52,43 +50,66 @@ export default function FormController(
switch (e.type) {
case VALUE_CHANGE_EVENT_TYPE:
const newState = setValue(acc.value, e.statePath, e.newValue);
const newValidation = setValue(
acc.validation,
e.statePath,
e.validation,
);
const controlledNewState = applyControl(newState, this.props);
return {
type: VALUE_CHANGE_EVENT_TYPE,
value: controlledNewState,
oldValue: acc.value,
statePath: e.statePath,
validation: newValidation,
};
case SUBMIT_EVENT_TYPE:
return { type: SUBMIT_EVENT_TYPE, value: acc.value };
return {
type: SUBMIT_EVENT_TYPE,
value: acc.value,
validation: acc.validation,
};
case INITIAL_CHANGE_EVENT_TYPE:
return {
type: INITIAL_CHANGE_EVENT_TYPE,
value: e.newValue,
oldValue: acc.value,
validation: e.validation,
};
case VALIDATION_FAILED_EVENT_TYPE:
return {
type: VALIDATION_FAILED_EVENT_TYPE,
value: acc.value,
validation: e.validation,
};
default:
return acc;
}
},
{ value: this._getInitialValue(props) },
)
.do(e => console.log("FormController event:\n", e))
.do(
e =>
global.process &&
global.process.env &&
global.process.env.FORMALIZR_ENV === "DEBUG" &&
console.log("FormController event:\n", e),
)
.share();
this.unsubscribeSubmitObs = this.subject
.filter(e => e.type == SUBMIT_EVENT_TYPE)
.subscribe({
next: e => this._submit(e.value),
});
this.subject.filter(e => e.type == SUBMIT_EVENT_TYPE).subscribe({
next: e => this._submit(e.value),
});
this.valueChangeObs = this.subject
.filter(
e =>
e.type == VALUE_CHANGE_EVENT_TYPE ||
e.type == INITIAL_CHANGE_EVENT_TYPE,
e.type == INITIAL_CHANGE_EVENT_TYPE ||
e.type == VALIDATION_FAILED_EVENT_TYPE,
)
.publishBehavior({
type: INITIAL_CHANGE_EVENT_TYPE,
value: this._getInitialValue(props),
validation: {},
})
.refCount();
this.rootDispatcherGetter = null;
Expand Down Expand Up @@ -139,7 +160,14 @@ export default function FormController(
checkIfModified === false ||
!is(newValue, fromJS(this._getInitialValue(this.props)))
) {
this.props.onSubmit(convertOut(newValue.toJS(), this.props));
const validation = validate(newValue.toJS(), this.props);
if (validation === true) {
this.props.onSubmit(convertOut(newValue.toJS(), this.props));
} else {
this.subject.next(
createValidationFailedEvent(new Validation(validation)),
);
}
}
}

Expand All @@ -154,10 +182,12 @@ export default function FormController(
return (
<WrappedComponent
{...this.props}
onChange={(value, statePath) =>
this.subject.next(createValueChangeEvent(value, statePath))}
onChange={(value: any, statePath: string, validation: Validation) =>
this.subject.next(
createValueChangeEvent(value, statePath, validation),
)}
valueChangeObs={this.valueChangeObs}
onSubmit={() => this.subject.next({ type: SUBMIT_EVENT_TYPE })}
onSubmit={() => this.subject.next(createSubmitChangeEvent())}
/>
);
}
Expand Down
65 changes: 65 additions & 0 deletions lib/FormEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use babel";
// @flow

import { Validation } from "./ValidationHelpers";

export const VALUE_CHANGE_EVENT_TYPE = "VALUE_CHANGE";
export const INITIAL_CHANGE_EVENT_TYPE = "INITIAL_CHANGE";
export const SUBMIT_EVENT_TYPE = "SUBMIT";
export const VALIDATION_FAILED_EVENT_TYPE = "VALIDATION_FAILED";

export type ValueChangeEventType = {
type: typeof VALUE_CHANGE_EVENT_TYPE,
newValue: any,
statePath: string,
validation: Validation,
};

export function createValueChangeEvent(
newValue: any,
statePath: string,
validation: Validation,
): ValueChangeEventType {
return {
type: VALUE_CHANGE_EVENT_TYPE,
newValue,
statePath,
validation,
};
}

export type InitialChangeEventType = {
type: typeof INITIAL_CHANGE_EVENT_TYPE,
newValue: any,
validation: Validation,
};

export function createInitialChangeEvent(newInitial: any): InitialChangeEventType {
return {
type: INITIAL_CHANGE_EVENT_TYPE,
newValue: newInitial,
validation: new Validation(),
};
}

export type SubmitChangeEventType = {
type: typeof SUBMIT_EVENT_TYPE,
};

export function createSubmitChangeEvent(): SubmitChangeEventType {
return {
type: SUBMIT_EVENT_TYPE,
};
}

export type ValidationFailedEventType = {
type: typeof VALIDATION_FAILED_EVENT_TYPE,
validation: Validation,
};

export function createValidationFailedEvent(validation: Validation): ValidationFailedEventType {
return {
type: VALIDATION_FAILED_EVENT_TYPE,
validation: validation,
};
}
50 changes: 37 additions & 13 deletions lib/StateDispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@
import React from "react";
import { fromJS, is, List } from "immutable";
import StateProxy from "./StateProxy";
import { setValue, getValue } from "./StateValue";
import { setValue, getValue } from "./StateValueHelpers";
import Rx from "rxjs";
import { INITIAL_CHANGE_EVENT_TYPE } from "./FormController";
import { INITIAL_CHANGE_EVENT_TYPE } from "./FormEvents";

export default function StateDispatcher(
convertIn = (v, props) => v,
convertOut = (v, props) => v,
convertIn: (v: any, props: Props) => any = (v, props) => v,
convertOut: (v: any, props: Props) => any = (v, props) => v,
) {
return function(WrappedComponent) {
return function(WrappedComponent: React$Component<any, any, any>) {
class Dispatcher extends React.Component {
subscription: Rx.Subscription;
handlers: {[key: string]: {
get: () => any,
set: (value: any) => void,
}};
valueChangeObs: Rx.Observable;

constructor(props) {
super(props);

Expand All @@ -25,6 +32,7 @@ export default function StateDispatcher(
else
return Object.assign({}, e, {
value: convertedValue,
validation: e.validation,
statePath: e.type === INITIAL_CHANGE_EVENT_TYPE
? undefined
: props.statePath,
Expand All @@ -35,12 +43,17 @@ export default function StateDispatcher(
componentDidMount() {
if (this.context.attachToController != null)
this.context.attachToController(() => this.getUncontrolledState());
this.unsubscribeInitial = this.valueChangeObs
this.subscription = this.valueChangeObs
.filter(e => e.type == INITIAL_CHANGE_EVENT_TYPE)
.do(e =>
console.log(
`Dispatching uncontrolled values from ${this.props.statePath}`,
))
.do(
e =>
global.process &&
global.process.env &&
global.process.env.FORMALIZR_ENV === "DEBUG" &&
console.log(
`Dispatching uncontrolled values from ${this.props.statePath}`,
),
)
.subscribe({
next: e => this.dispatchUncontrolledValues(e),
});
Expand All @@ -49,7 +62,7 @@ export default function StateDispatcher(
componentWillUnmount() {
if (this.context.attachToController != null)
this.context.attachToController(null);
this.unsubscribeInitial.unsubscribe();
this.subscription.unsubscribe();
}

dispatchUncontrolledValues(event) {
Expand All @@ -69,7 +82,7 @@ export default function StateDispatcher(
return {
valueChangeObs: this.valueChangeObs,
rootValueChangeObs: this.props.rootValueChangeObs,
onStateChange: (v, statePath) => {
onStateChange: (v, statePath, validation) => {
const convertedInParentValue = convertIn(
this.props.getValue(),
this.props,
Expand All @@ -96,11 +109,16 @@ export default function StateDispatcher(
convertedValue !== completeValue ||
typeof convertedValue != typeof this.props.getValue()
)
this.props.onChange(convertedValue, this.props.statePath);
this.props.onChange(
convertedValue,
this.props.statePath,
validation,
);
else
this.props.onChange(
v,
`${this.props.statePath}${this.props.statePath == "" || statePath == "" ? "" : "."}${statePath}`,
validation,
);
},
attachToController: null,
Expand Down Expand Up @@ -152,6 +170,7 @@ export default function StateDispatcher(
attachToController: React.PropTypes.func,
};


return StateProxy(
{ root: true },
{
Expand All @@ -161,3 +180,8 @@ export default function StateDispatcher(
)(Dispatcher);
};
}

type Props = {
valueChangeObs?: Rx.Observable;
onChange?: (v: any) => void;
};
6 changes: 3 additions & 3 deletions lib/StateDispatcher.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Rx from "rxjs";
import {
VALUE_CHANGE_EVENT_TYPE,
INITIAL_CHANGE_EVENT_TYPE,
} from "./FormController";
} from "./FormEvents";

describe("StateDispatcher", () => {
describe("Controlled", () => {
Expand Down Expand Up @@ -102,7 +102,7 @@ describe("StateDispatcher", () => {

obs.subscribe(e => {
subject.find(InputField).at(1).prop("onChange")("Joe");
expect(spy).toBeCalledWith("Joe", "lastname");
expect(spy).toBeCalledWith("Joe", "lastname", true);
done();
});
});
Expand Down Expand Up @@ -190,7 +190,7 @@ describe("StateDispatcher", () => {

obs.subscribe(e => {
subject.find(InputField).at(1).prop("onChange")("Joe");
expect(spy).toBeCalledWith("Joe", "lastname");
expect(spy).toBeCalledWith("Joe", "lastname", true);
done();
});
});
Expand Down
Loading

0 comments on commit 4410d6c

Please sign in to comment.