Skip to content

Commit

Permalink
fix flow typing | remove createSender API
Browse files Browse the repository at this point in the history
  • Loading branch information
vovacodes committed Jul 21, 2018
1 parent b0b2e6e commit 54950c6
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 90 deletions.
2 changes: 1 addition & 1 deletion __tests__/Context-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("ReComponent", () => {
class Container extends ReComponent {
constructor() {
super();
this.handleClick = this.createSender("CLICK");
this.handleClick = () => this.send({ type: "CLICK" });
this.state = { count: 0 };
}

Expand Down
6 changes: 3 additions & 3 deletions __tests__/ReComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe("ReComponent", () => {
class Example extends ReComponent {
constructor() {
super();
this.handleClick = this.createSender("CLICK");
this.handleClick = () => this.send({ type: "CLICK" });
this.state = { count: 0 };
}

Expand Down Expand Up @@ -95,7 +95,7 @@ describe("ReComponent", () => {
class Example extends ReComponent {
constructor() {
super();
click = this.createSender("CLICK");
click = () => this.send({ type: "CLICK" });
}

static reducer(action, state) {
Expand Down Expand Up @@ -123,7 +123,7 @@ describe("ReComponent", () => {
class ClassPropertyReducer extends ReComponent {
constructor() {
super();
click = this.createSender("CLICK");
click = () => this.send({ type: "CLICK" });
}

reducer(action, state) {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/ReComponentImmutable-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe("ReComponentImmutable", () => {
class Example extends ReComponent {
constructor() {
super();
this.handleClick = this.createSender("CLICK");
this.handleClick = () => this.send({ type: "CLICK" });
}

initialImmutableState(props) {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/RePureComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("RePureComponent", () => {
class Example extends RePureComponent {
constructor() {
super();
this.handleClick = this.createSender("CLICK");
this.handleClick = () => this.send({ type: "CLICK" });
this.state = { count: 0 };
}

Expand Down
12 changes: 6 additions & 6 deletions __tests__/UpdateTypes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ describe("UpdateTypes", () => {
class ReducerReturns extends ReComponent {
constructor() {
super();
noUpdate = this.createSender("NO_UPDATE");
update = this.createSender("UPDATE");
sideEffects = this.createSender("SIDE_EFFECTS");
updateWithSideEffects = this.createSender("UPDATE_WITH_SIDE_EFFECTS");
invalid = this.createSender("INVALID");
unhandled = this.createSender("UNHANDLED");
noUpdate = () => this.send({ type: "NO_UPDATE" });
update = () => this.send({ type: "UPDATE" });;
sideEffects = () => this.send({ type: "SIDE_EFFECTS" });
updateWithSideEffects = () => this.send({ type: "UPDATE_WITH_SIDE_EFFECTS" });
invalid = () => this.send({ type: "INVALID" });
unhandled = () => this.send({ type: "UNHANDLED" });
this.state = { count: 0 };
}

Expand Down
21 changes: 0 additions & 21 deletions src/re.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ export function Re(Component) {
// and return either `NoUpdate()`, `Update(state)`, `SideEffects(fn)`, or
// `UpdateWithSideEffects(state, fn)`.
//
// To avoid defining functions that call `ReComponent#send` in the render
// method, we also expose a convenience method: `ReComponent#createSender`.
//
// @see https://git.io/vh2AY
this.send = action => {
let sideEffects;
Expand Down Expand Up @@ -150,24 +147,6 @@ export function Re(Component) {

setState.call(this, updater, () => sideEffects && sideEffects(this));
};

// Convenience method to create sender functions: Functions that send an
// action to the reducer. The created actions will follow the naming
// conventions of [flux-standard-actions].
//
// If the sender is called with an argument (like an Event object for an
// event callback), the first argument will be exposed as the `payload`.
// Note that subsequent arguments to a sender are ignored for now.
//
// [flux-standard-actions]: https://github.com/redux-utilities/flux-standard-action
this.createSender = type => {
return payload => {
this.send({
type,
payload
});
};
};
}
};
}
18 changes: 7 additions & 11 deletions type-definitions/ReComponent.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import * as React from "react";

declare opaque type UpdateType;

export type Sender<AT, A = mixed> = (a: A) => { action: AT, payload: A };

declare export function NoUpdate(): {| type: UpdateType |};
declare export function Update<S>(state: S): {| type: UpdateType, state: S |};
declare export function SideEffects<T>(
Expand All @@ -22,27 +20,25 @@ declare export function UpdateWithSideEffects<S, T>(

declare export class ReComponent<
Props,
State = void,
ActionType = string
State,
Action: { +type: string },
> extends React.Component<Props, State> {
initialState(props: Props): State;

static reducer(
action: { type: ActionType },
action: Action,
state: State
):
| {| type: UpdateType |}
| {| type: UpdateType, state: $Shape<State> |}
| {| type: UpdateType, sideEffects: ($Subtype<ReComponent<Props, State, ActionType>>) => mixed |}
| {| type: UpdateType, state: $Shape<State>, sideEffects: ($Subtype<ReComponent<Props, State, ActionType>>) => mixed |};

send(action: { type: ActionType, payload?: mixed }): void;
| {| type: UpdateType, sideEffects: ($Subtype<ReComponent<Props, State, Action>>) => mixed |}
| {| type: UpdateType, state: $Shape<State>, sideEffects: ($Subtype<ReComponent<Props, State, Action>>) => mixed |};

createSender<AT: ActionType>(actionType: ActionType): Sender<AT>;
send(action: Action): void;

// This type is only used when initialState returns an Immutable.js data type.
// Consider this API unstable.
+immutableState: State;
}

declare export class RePureComponent<P, S, A> extends ReComponent<P, S, A> {}
declare export class RePureComponent<P, S, A: { +type: string }> extends ReComponent<P, S, A> {}
117 changes: 71 additions & 46 deletions type-definitions/__tests__/ReComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,9 @@ import {
UpdateWithSideEffects
} from "../../";

class UntypedActionTypes extends ReComponent<{}, { count: number }> {
handleClick = this.createSender("CLICK");
// $ExpectError
handleFoo = this.createSender();

state = { count: 0 };
type Action = {| type: "A" |} | {| type: "B" |} | {| type: "C" |} | {| type: "D" |}

static reducer(action, state) {
switch (action.type) {
case "CLICK":
return Update({ count: state.count + 1 });
default:
return NoUpdate();
}
}
}
const untypedActionTypes = new UntypedActionTypes();
untypedActionTypes.send({ type: "CLICK" });
untypedActionTypes.send({ type: "CLACK" });
// $ExpectError
untypedActionTypes.send({});

untypedActionTypes.handleClick();
untypedActionTypes.handleClick({});
untypedActionTypes.handleClick(1);
// $ExpectError
untypedActionTypes.handleClick({}, {});

class StateMismatch extends ReComponent<{}, { count: number }> {
class StateMismatch extends ReComponent<{}, { count: number }, Action> {
// $ExpectError
state = { invalid: "state" };

Expand All @@ -50,16 +24,16 @@ class StateMismatch extends ReComponent<{}, { count: number }> {
case "B":
return Update({ count: 1 });
case "C":
// $ExpectError
// $ExpectError - `count` should be `number`
return Update({ count: "1" });
default:
// $ExpectError
// $ExpectError - `invalid` is missing in State
return Update({ invalid: "state" });
}
}
}

class UpdateTypes extends ReComponent<{}, { count: number }> {
class UpdateTypes extends ReComponent<{}, { count: number }, Action> {
// Used to test the callback property of SideEffects
someClassProperty: number;

Expand All @@ -72,26 +46,26 @@ class UpdateTypes extends ReComponent<{}, { count: number }> {
case "C":
return SideEffects((instance: UpdateTypes) => {
instance.someClassProperty = 1;
// $ExpectError
// $ExpectError - `instance.someClassProperty` has to be number
instance.someClassProperty = "1";
});
default:
return UpdateWithSideEffects({ count: 1 }, (instance: UpdateTypes) => {
instance.someClassProperty = 1;
// $ExpectError
// $ExpectError - `instance.someClassProperty` has to be number
instance.someClassProperty = "1";

});
}
}
}

class TypedActionTypes extends ReComponent<{}, { count: number }, "CLICK"> {
handleClick = this.createSender("CLICK");
// $ExpectError
handleFoo = this.createSender("CLACK");
// $ExpectError
handleBar = this.createSender();
class TypedActionTypes extends ReComponent<
{},
{ count: number },
{| type: 'CLICK' |}
> {
handleClick = () => this.send({ type: 'CLICK' });

static reducer(action, state) {
switch (action.type) {
Expand All @@ -105,16 +79,16 @@ class TypedActionTypes extends ReComponent<{}, { count: number }, "CLICK"> {

const typedActionTypes = new TypedActionTypes();
typedActionTypes.send({ type: "CLICK" });
// $ExpectError
// $ExpectError - "CLACK" is invalid action type
typedActionTypes.send({ type: "CLACK" });
// $ExpectError
// $ExpectError - invalid action
typedActionTypes.send({});

typedActionTypes.handleClick();
// $ExpectError - `handleClick` expects no arguments
typedActionTypes.handleClick({});
// $ExpectError - `handleClick` expects no arguments
typedActionTypes.handleClick(1);
// $ExpectError
typedActionTypes.handleClick({}, {});

// Flow can verify that we've handled every defined action type for us through
// what is called [exhaustiveness testing].
Expand All @@ -127,27 +101,29 @@ typedActionTypes.handleClick({}, {});
const absurd = <T>(x: empty): T => {
throw new Error("absurd");
};

class ExhaustivelyTypedFailingActionTypes extends ReComponent<
{},
{ count: number },
"CLICK" | "CLACK"
{| type: 'CLICK' |} | {| type: 'CLACK' |}
> {
static reducer(action, state) {
switch (action.type) {
case "CLICK":
return NoUpdate();
default: {
// $ExpectError
// $ExpectError - should be unreachable
absurd(action.type);
return NoUpdate();
}
}
}
}

class ExhaustivelyTypedPassingActionTypes extends ReComponent<
{},
{ count: number },
"CLICK" | "CLACK"
{ type: "CLICK" } | { type: "CLACK" }
> {
static reducer(action, state) {
switch (action.type) {
Expand All @@ -162,3 +138,52 @@ class ExhaustivelyTypedPassingActionTypes extends ReComponent<
}
}
}


class FailingPayloadType extends ReComponent<
{},
{ count: number, awesome: boolean },
{ type: "CLICK", payload: number } | { type: "CLACK", payload: boolean }
> {
// $ExpectError - `clicks` should be `number`
handleClick = (clicks: boolean) => this.send({ type: 'CLICK', payload: clicks });
// $ExpectError - `awesome` should be `boolean`
handleClack = (awesome: number) => this.send({ type: 'CLACK', payload: awesome });

static reducer(action, state) {
switch (action.type) {
case "CLICK":
// $ExpectError - `awesome` should be `boolean`, but received `number`
return Update({ awesome: action.payload });
case "CLACK":
// $ExpectError - `count` should be `number`, but received `boolean`
return Update({ count: action.payload });
default: {
absurd(action.type);
return NoUpdate();
}
}
}
}

class PassingPayloadType extends ReComponent<
{},
{ count: number, awesome: boolean },
{ type: "CLICK", payload: number } | { type: "CLACK", payload: boolean }
> {
handleClick = (clicks: number) => this.send({ type: 'CLICK', payload: clicks });
handleClack = (awesome: boolean) => this.send({ type: 'CLACK', payload: awesome });

static reducer(action, state) {
switch (action.type) {
case "CLICK":
return Update({ count: action.payload });
case "CLACK":
return Update({ awesome: action.payload });
default: {
absurd(action.type);
return NoUpdate();
}
}
}
}

0 comments on commit 54950c6

Please sign in to comment.