Empathetic Persuasion

One bad habit of mine is to share materials to make a point instead of making a point myself. But my attempts usually weren’t successful despite the merits of those materials. I wondered why people didn’t see the “light.” But, recently, why sharing articles, reports, or some data rarely persuades anyone clicked in my head.

Persuasion and following commitment do not originate from our rational mind especially if the problem space is complex and the benefits and the costs are not obvious. Rather, they happen at the emotional level. So you change behaviors when you have others feel your pain.

For example, if I want to avoid slow review turnaround time and resulting big pull requests, the best way to persuade is to vividly describe how I feel that longer review cycles slow down my projects.

It is not easy to do, I know. I also keep forgetting this. But next time I feel the urge to share an article, I will take a step back and think about the pain I have at the moment.

English as Second Language Colleagues

Recently I was appalled to hear that my friend, who is a software engineer, was denied an internal transfer because his potential manager did not want a non-native English speaker on his team. As a native Korean speaker myself, it was unsettling to hear such a story. I thought that in this day and age, especially at Silicon Valley, we have figured out how to work together with those from different backgrounds.

I realize I don’t see many conversations around how this lack of fluency impacts some of us. I suspect that’s because most writers and speakers are already fluent in their languages. So we don’t talk about this issue and put the onus on the individuals.

We can’t expect people who spent most of their lives in different languages to understand all connotations and cultural subtext. It is not going to happen no matter how hard they try. Even some native speakers have a hard time communicating well. If we believe that communication is a two-way street, we have to meet in the middle.

Yes, I get that it makes communication more difficult. But we, collectively speaking, hired each of us because everyone was deemed qualified to do the job. Then, it is on all of us to create an environment where we all can be successful.

How? I admit I don’t have a great idea. But I will start by setting up 1:1s to build the relationship and the context. That will provide a sometimes necessary bridge for the lapses. If I notice how well or badly we communicate, I will share that too. And I will always try to remember that none of us is perfect. Language proficiency, or the lack thereof, is just another imperfection some of us have.

Promise.any and Promise.allSettled

One benefit of Javascript proposal process is that there are always new things to learn and to make things more interesting. Promise.any and Promise.allSettled are not revolutionary but they will enable a new, more concise way to code. You can read more about them from here.

A caveat I found is that Promise.allSettled will never reject. It does make sense but at first I found myself thinking, “so when does it reject and what does it reject with?” I am interested to see how this behavior will be typed in Typescript.

Don’t Just Link, Summarize

This came up during a meeting to review how we organize our work (basically JIRA).

In this day and age, everything is linkable; whether it be a Slack thread, a Zendesk ticket or a Google Doc. So in order to give “context,” it is tempting to just throw a link in a conversation. But you can do more than that.

I wouldn’t say linking does not have its own value because discovery is a real problem. However, every one of us is overloaded with information. We can’t be expected to go down every rabbit hole of links. So, help us out a little. Give us a link, and summarize it a little for us: What’s in it? Why do you think this context is useful? This will save us good 10-20 minute for every link and we will appreciate you going extra mile for us.

When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.HTML Spec

I was looking into a bug that an embedded form would die due to the security restriction when you press enter inside the input. It turns out this random behavior was causing the issue ūü§∑

(I concede that we should handle form submit properly though)

How to Fight Complexity

Complexity is the bane of all software projects. Not all code may be debt, but all will increase the complexity. There are two ways to fight this complexity. We can enhance our ability to handle complexity with new abstractions, thorough tests and more documentation (the engineering way). Or, we can write less code (the product way).

My go-to approach has been to the engineering way. To build enough safety nets to protect myself from the complexity. This requires less product deliberation and promotes a bias towards action (“as long as things are tested, we are good”). However, I tend not to think critically about new proposed features. This article on saying “no” reminded me of the other half of the methods I have ignored.

Empathy over Slack

I often find myself that I react very defensively when I chat over Slack vs. in person. While reading this article on Consious Leadership, I just realized why: when I am just looking at a screen, I forget that I am talking to a human being. The discussions feel more abstracted and more about ideas and I lose my empathy. It is good to know why and now I can improve upon it.

Yet another JSON validator

https://github.com/joanllenas/ts.data.json

My first reaction was “well, you should use GraphQL.” Or, even if you don’t have control over your APIs, having two sources of truth feel very cumbersome just to validate data. I believe a better approach will be to generate a JSON validator based on the type information at compile time such as babel-blade, or react-docgen-typescript-loader. But then again, I don’t have a clear plan to achieve that, either.

Part 2: Typescript+Redux Best Practice at Vingle

Part 1: History of Redux State Management at Vingle

In this part two, I am going to describe our team’s current best practices to make Typescript work for you when working with Redux.

  • Creating Type-safe Actions and Reducers
  • Properly typing Redux Container

Creating Type-safe Actions and Reducers

Considering how reducers are just simple functions that accept two arguments, you would expect Typescript to work well with those two. States do. But actions, because¬†dispatch¬†accepts any types of arguments, cannot be typed safely without developers’ involvement. If you don’t type your actions, your reducer will end up in the not-so-ideal state:

function reducer(state = INITIAL_STATE, action: Redux.Action) {
switch (action.type) {
case ActionTypes.FETCH_USER: {
// simple case
return {
state,
userId: (action.payload as any).userId,
};
}
default: {
return state;
}
}
}
view raw type-safe-reducer.ts hosted with ❤ by GitHub

You can catch some of type errors with unit tests, but you will miss some properties and lose easy refactoring provided by Typescript. To acheive type-safety before Typescript 2.8, you could use string enum:

enum ActionTypes {
FETCH_USER = "FETCH_USER",
}
interface IFetchUserAction {
type: ActionTypes.FETCH_USER;
payload: { userId: string }
}
interface IOtherAction {
type: "____________________";
}
type Actions = IFetchUserAction | IOtherAction;
function fetchUser(userId: string): IFetchUserAction {
return {
type: ActionTypes.FETCH_USER,
payload: {
userId,
}
};
}
function reducer(
state = INITIAL_STATE,
action: Actions,
): IState {
switch (action.type) {
case ActionTypes.FETCH_USER: {
// in this closure, Typescript knows that action is of interface IFetchUserAction, thanks to enum ActionTypes.
return {
state,
userId: action.payload.userId,
};
}
default: {
return state
}
}

IOtherAction¬†is needed so that Typescript won’t complain about default case in switch statement (that is, exhaustiveness checking). This works OK if you ignore the fact that there are essentially two duplicate type definitions in your action interfaces, and action creators. Starting with¬†Typescript 2.8, you can use¬†ReturnType¬†to remove action interfaces. The code below is our way to type actions and reducers.

import { ActionCreatorsMapObject } from "redux";
// interface ActionCreatorsMapObject {
// [key: string]: ActionCreator<any>;
// }
type ActionUnion<T extends ActionCreatorsMapObject> = ReturnType<
T[keyof T]
>;
enum ActionTypes {
FETCH_USER = "FETCH_USER",
}
function createAction<T extends { type: ActionTypes }>(d: T): T {
return d;
}
export const ActionCreators = {
fetchUser(payload: {userId: string}) =>
createAction({type: ActionTypes.FETCH_USER, payload}),
}
type Actions = ActionUnion<typeof ActionCreators>;
function reducer(
state = INITIAL_STATE,
action: Actions,
): IState {
switch (action.type) {
case ActionTypes.FETCH_USER: {
// in this closure, Typescript knows that action is of ActionCreators.fetchUser's ReturnType.
return {
state,
userId: action.payload.userId,
};
}
default: {
return state
}
}

Typing Redux Container components

Typing Redux container components correctly is important to use, and test the components correctly. Before our team learned how to type components, we ended up with tests like this:

const Container = (props: { data: any; dispatch: Dispatch<any> }) => {
// render something and do something useful
return <div />;
};
const ConnectedContainer = connect()(Container);
describe("", () => {
let wrapper: ReactWrapper;
beforeEach(() => {
const store = mockStore(state);
wrapper = mount(<ConnectedContainer data dispatch={store.dispatch} />, { store });
});
});

So let’s dive in.

Before you try to type Redux container components properly, you need to understand the type definition of connect. Carefully read the code below I quoted from Redux type definition (comments are mine). The definition uses a lot of type overloading but I will go through some cases to help you understand what exactly goes on.

Please note that the definitions below are from @types/react-redux@5.0.19.

When you don’t pass in any argument to connect

This is when you only need dispatch inside your container.

const Container = (props: { data: any; dispatch: Dispatch<any> }) => {
// render something and do something useful
return <div />;
};
export default connect()(Container);
view raw simple-container.ts hosted with ❤ by GitHub

As there are no arguments to connect, all connect will do is to inject dispatch<any> into props.

When you pass in mapStateToProps to connect

If you want to map only state to props, say for render only components, you

type SearchData = { query: string };
type AppState = {
searchData: SearchData;
};
type Props = { query: string; data: any; dispatch: Dispatch<any> };
function mapStateToProps(state: AppState) {
return {
query: state.searchData.query,
};
}
const Container = (_props: Props) => {
// render something and do something useful
return <div />;
};
const A = connect(mapStateToProps)(Container);
<A data />; // this is valid
<A data dispatch={store.dispatch} />; // this isn't valid
view raw mapped-container.ts hosted with ❤ by GitHub

It almost looks like a magic as Redux type definition does a lot of heavy lifting for us. Let’s examine what actually happens inside the code above.

interface Connect {
<TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = {}>(
mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
): InferableComponentEnhancerWithProps<
TStateProps & DispatchProp<any> & TOwnProps,
TOwnProps
>;
}

This^ connect definition is the overloaded type definition used. In the definition, mapStateToProps is expanded to

(initialState: State, ownProps: TOwnProps) => (
state: State,
ownProps: TOwnProps,
) => TStateProps;

So Typescript will infer TStateProps, and State to be {query: string}, and AppState from the argument mapStateToProps. InferableComponentEnhancerWithProps is expanded to

<P extends (TStateProps & DispatchProp<any> & TOwnProps)>(component: Component<P>): ComponentClass<Omit<P, keyof (TStateProps & DispatchProp<any> & TOwnProps)> & TOwnProps> & {WrappedComponent: Component<P>}
view raw container-type.ts hosted with ❤ by GitHub

And Typescript will infer P to be Props, and check whether the container component’s props is larger than the union of TStatePropsDispatchProp<any>, and TOwnProps.

If I put the logic above into code, it looks like the following:

type TStateProps = ReturnType<typeof mapStateToProps>;
type TOwnProps = Omit<Props, keyof TStateProps | keyof DispatchProp<any>>; // this results in { data: any }. But this isn't necessary and you can use {} without a problem.
const B = connect<TStateProps, {}, TOwnProps, AppState>(mapStateToProps)(
Container,
);
<B data />; // this is valid
<B data dispatch={store.dispatch} />; // this isn't valid
view raw summary.ts hosted with ❤ by GitHub

When you pass in both mapStateToProps and mapDispatchToProps to connect

This isn’t hard to understand once you understood how Redux type definition handles¬†mapStateToProps.¬†mapDispatchToProps¬†is treated like¬†mapStateToProps. For your reference, I included the overloaded type below.

interface Connect {
<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = {}>(
mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
): InferableComponentEnhancerWithProps<
TStateProps & TDispatchProps & TOwnProps,
TOwnProps
>;
}
view raw both-container.ts hosted with ❤ by GitHub

When you also pass in mergeProps

This is also rather straightforward. Instead of merging TStatePropsTDispatchProps, and TOwnProps naively for the component definition, Connect will now depend on mergeProps to merge these props. The only additional check, (or inference) is whether mergeProps is of type (stateProps: TStateProps, dispatchProps: TDispatchProps, ownProps: TOwnProps): TMergedProps;.

What this means

First of all, congratulations on getting through all these different types! Now you get how Connect works. But, it turns out you don’t need to type things directly when you use Redux’s Connect. However, other HOC’s definitions will vary, and you will need to learn how their type systems work.

Extracredit (Typescript tips not related to Redux)

Know your types in React

Knowing React types helps your code to work with React seamlessly. Here is the usual go-to list for us.

React.Component<P, S>
React.StatelessComponent<P>
React.ReactElement = instantiated React Component
React.ReactNode = React.ReactElement + Renderable primitive types (object is not valid). `children` has this type
React.CSSProperties
React.ReactEventHandler
React.<Input>Event
React.HTMLProps<ElementType> = Used to extend your component props. Ex) TOwnProps & React.HTMLProps<HTMLDivElment>

How to type HOCs that inject props

The following code is an excerpt from react-intl. This type definition is straight-forward to set up, but expects the users of the library to know which props are injected into.

interface InjectedIntlProps {
intl: InjectedIntl;
}
function injectIntl<P>(
component: ComponentConstructor<P & InjectedIntlProps>,
options?: InjectIntlConfig,
): React.ComponentClass<P> & {
WrappedComponent: ComponentConstructor<P & InjectedIntlProps>;
};
// actual usage
interface IProps {
flag: boolean;
}
class Toast extends React.PureComponent<IProps & InjectedIntlProps> {
}
export default injectIntl<IProps>(Toast);
view raw hoc-types.ts hosted with ❤ by GitHub

Use Ambient Types to simplify your dependencies

This is an easy-to-miss option when you first start using Typescript. You should use typeRoots option to avoid adding unnecessary dependencies.

Afterword

As we develop, and maintain our React apps, we have encountered many bugs. Based on our experience, the harder-to-track, and more critical bugs often stemmed from typeless part of the code. That is why we are determined to type things both comprehensively, and correctly. This isn’t the farthest we can go with Typescript, but this is where we are at, and I hope this article has helped you understand Typescript and Redux more deeply.

Part 1: History of Redux State Management at Vingle

Part 2: Typescript+Redux Best Practice at Vingle

This post is a repost of my post at Vingle Tech Blog.

In this two-part post, I am going to go over the different flavors of Redux state management at Vingle and our thought process behind each iterations we went through over the last year and half. I hope this post guide how you structure your Redux states.

Genesis: Redux + Immutable.Map

My team chose React to create a small-scale mobile marketing website as a learning experiment. Our main project, at the time, was based on Rails, and Angular 1, and we were separating web applications from Rails to simplify, and speed up our deployment process. That meant we had to create everything from scratch: a new build pipeline, a new webpack configuration, while learning about the vast React ecosystem.

We heard that Redux simplifies debugging application states greatly, and, with the nightmarish memories of debugging Angular 1’s watchers, chose to adopt Redux. We also learned a bit about shouldComponentUpdate and React’s component lifecycle, and wanted to have an immutable state. I was already familiar with high-order immutable objects from my previous work (this), so Immutable.js was an obvious choice.

In the end, we have Redux setup looking like this:

import { Map } from "immutable";
// reducers
const INITIAL_STATE = Map({ post: null, isLoading: false });
function postReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case "FETCHED": {
return state.withMutations(currentState =>
currentState.set("post", action.payload.post).set("isLoading", false),
);
}
default: {
return state;
}
}
}
// action creators
function fetchedPost(post) {
return {
type: "FETCHED",
payload: {
post,
},
};
}
view raw genesis.js hosted with ❤ by GitHub

1st Iteration: Typescript + Redux + Immutable.Map

Once we have gotten more used to React, and Redux, and proven that we could develop new features much faster on the mobile page, we started migrating our main web application to React. But unlike the proof-of-concept mobile page, this app would have dozens of routes and reducers, and much more complex components, so we chose to use Typescript for this app.

Unfortunately, Immutable.Map with different types of values (number, boolean, other Maps, or Lists, for example) does not play well with Typescript. The following is a Typescript definition of Immutable.Map:

interface Keyed<K, V> extends Collection<K, V>, Iterable.Keyed<K, V> {}
interface Map<K, V> extends Keyed<K, V> {
set(key: K, value: V): Map<K, V>;
setIn(keyPath: Array<any>, value: any): Map<K, V>;
}
view raw first-iteration-1.ts hosted with ❤ by GitHub

As you can see, there isn’t a good way to specify different types of a Immutable.Map’s values. So we ended up doing this hacky workaround.

// scaffolding
interface IPostStateImmutable {
get(key: "post"): IPostImmutable | null; // IPostImmutable is also another hacky interface like IPostStateImmutable.
get(key: "isLoading"): boolean;
set(key: "post", value: IPostImmutable | null): IPostStateImmutable;
set(key: "isLoading", value: boolean): IPostStateImmutable;
withMutations(
mutator: (mutable: IPostStateImmutable) => IPostStateImmutable,
): IPostStateImmutable;
}
// reducers
const INITIAL_STATE: IPostStateImmutable = Map({
post: null,
isLoading: false,
});
function postReducer(
state: IPostStateImmutable = INITIAL_STATE,
action,
): IPostStateImmutable {
switch (action.type) {
case "FETCHED": {
return state.withMutations(currentState =>
currentState.set("post", action.payload.post).set("isLoading", false),
);
}
case "UPDATED_TITLE": {
return state.setIn(["post", "title"], action.payload.title);
}
default: {
return state;
}
}
}
// actions stay the same
view raw first-iteration-2.ts hosted with ❤ by GitHub

Needless to say, this pattern is painful to maintain, and hard to guarantee correctness. Typescript got in the way rather than helping us.

2nd Iteration: Typescript + Redux + Immutable.Record

So we looked for a better way to tie Typescript and Immutable.js together. Then we found that there was another Immutable class called Immutable.Record and a library called typed-immutable-record. With the library, we created a type-safe Immutable Record:

import { TypedRecord, recordify } from "typed-immutable-record";
// scaffolding
interface IPostState {
post: IPost | null;
isLoading: boolean;
}
interface IPostStateRecordPart {
post: IPostRecord; // this interface is created in a similar fashion.
isLoading: boolean;
}
interface IPostStateRecord
extends TypedRecord<IPostStateRecordPart>,
IPostStateRecord {}
function recordifyPostState(plainState: IPostState): IPostStateRecord {
return recordify<IPostStateRecordPart, IPostStateRecord>({
post: plainState.post
? recordify<IPostRecordPart, IPostRecord>(plainState.post)
: null,
isLoading: plainState.isLoading,
});
}
// reducers
const INITIAL_STATE: IPostStateRecord = recordifyPostState({
post: null,
isLoading: false,
});
function postReducer(
state: IPostStateRecord = INITIAL_STATE,
action,
): IPostStateRecord {
switch (action.type) {
case "FETCHED": {
return state.withMutations(currentState =>
currentState.set("post", action.payload.post).set("isLoading", false),
);
}
case "UPDATED_TITLE": {
return state.setIn(["post", "title"], action.payload.title);
}
default: {
return state;
}
}
}
view raw second-iteration.ts hosted with ❤ by GitHub

It took some time for us to understand how to scaffold Record interfaces correctly but we managed to create type-safe redux states with both dot notations, and helper methods like setIn or withMuationsHowever, as you can see from the code above, we had to create a large number of interfaces, especially when our states were deeply nested. Once we got the pattern down, it wasn’t difficult to follow the pattern but it was a lot of work which disincentivized our team to create smaller, and isolated reducers. But we didn’t know any better, so we carried on.

3rd Iteration: Typescript + Redux + Typescript Readonly Interfaces

During a random conversation with an engineer at another startup, I learned about readonly properties in Typescript, and realized those properties could replace Immutable.js completely.

// scaffolding
interface IPostState
extends Readonly<{
post: IPost | null;
isLoading: boolean;
}> {} // this has to be a Readonly interface as well.
// reducers
const INITIAL_STATE: IPostState = {
post: null,
isLoading: false,
};
function postReducer(state: IPostState = INITIAL_STATE, action): IPostState {
switch (action.type) {
case "FETCHED": {
return {
post: action.payload.post,
isLoading: false,
};
}
case "UPDATED_TITLE": {
return {
state,
post: {
state.post,
title: action.payload.title,
},
};
}
default: {
return state;
}
}
}
view raw third-iteration-1.ts hosted with ❤ by GitHub

By using Readonly interfaces, the scaffolding is reduced to a quarter by removing RecordPartRecord, and recordify. However, there is a problem with this approach when you need to update deeply; the case above UPDATED_TITLE is such an example. During the conversion, we had some codes go out of hand like this:

return {
state,
post: {
state.post,
author: {
state.post.author,
relation: {
state.post.author.relation,
following: true,
},
},
},
};

4th Iteration: Typescript + Redux + Typescript Readonly Interfaces + Normalizr

We could solve this problem by adopting a deep merge library, but we feared that those libraries may not be type-safe. After giving some thoughts, we determined that the real problem was with the deeply nested structures of our states and planned to flatten the states by normalizing. Of the two popular normalizing libraries, redux-orm, and normalizr, we chose the latter for its simplicity.

Our final, and current version of redux looks like the following:

// post reducer
interface IPostState
extends Readonly<{
postId: number | null;
isLoading: boolean;
}> {}
const INITIAL_STATE: IPostState = {
postId: null,
isLoading: false,
};
function postReducer(state: IPostState = INITIAL_STATE, action): IPostState {
switch (action.type) {
case "FETCHED": {
return {
postId: action.payload.postId,
isLoading: false,
};
}
default: {
return state;
}
}
}
// normalized entity reducer
interface IEntityState
extends Readonly<{
posts: {
[postId: number]: INormalizedPost;
};
}> {}
function entityReducer(
state: IEntityState = { posts: {} },
action,
): IPostState {
switch (action.type) {
case "ADD_ENTITIES": {
return {
state,
posts: {
state.posts,
entities.posts,
},
};
}
case "UPDATED_TITLE": {
const postToUpdate = state.posts[action.payload.postId];
if (!postToUpdate) {
return state;
}
return {
state,
posts: {
state.posts,
[action.payload.postId]: {
postToUpdate,
title: action.payload.title,
},
},
};
}
default: {
return state;
}
}
}
// action creators
function fetchedPost(postId: number) {
return {
type: "FETCHED",
payload: {
postId,
},
};
}
function addEntities(entities: Partial<IEntityState>) {
return {
type: "ADD_ENTITIES",
payload: {
entities,
},
};
}
// container component
function mapStateToProps(state: IAppState, _routeProps: any) {
return {
post: denormalize(state.postState.post, postEntity, state.entities),
};
}
view raw fourth-iteration.ts hosted with ❤ by GitHub

Afterword

When I look back, part of me regret that we didn’t do more research which could have saved a lot of time; this collection of redux-related libraries would have been helpful, and normalizing is already in official Redux documention. However, part of me also feel like we would have never appreciated the utility of these libraries and techniques because we didn’t know the downsides of not using those libraries and techniques. And that is why I wrote this post; I hope you understand what problems lie ahead and save yourself some time.