On any JS project my preference is to always use TypeScript, as it greatly increases my productivity (intellisense, autocompletion) while eliminating 90% of the bugs right there, at design time.
Over time I’ve gathered some useful snippets and patterns, out of which I’d like to share a couple I use most with you.
Hope you find these patterns useful as I do.
Connected Class Component
A Redux connected component has mapped properties which include mapped store properties and store actions. Wouldn’t it be nice if we could have these properties type-checked and auto-completed? Here is how I do it:
///////////////////
// Clock.ts
///////////////////
import { ConnectedComponentProps } from "./prop-utils";
import * as counterActions from "./actions";
import { connect } from "react-redux";
import { IRootState } from "./store";
import { Dispatch } from "redux";
import * as React from 'react';
// Store state mapping
const mapStateToProps = (state: IRootState) => ({
counterValue: state.counter,
});
// Actions mapping
const mapDispatchToProps = (dispatch: Dispatch) => ({
increment: () => dispatch(counterActions.incrementCounter()),
});
// Own component props
interface ICounterProps {
counterTitle: string;
}
// Combine own props with store state and store actions
type CounterProps = ConnectedComponentProps<
typeof mapDispatchToProps,
typeof mapStateToProps,
ICounterProps>;
class Counter extends React.Component<CounterProps> { // Note use of CounterProps
constructor(props: CounterProps) {
super(props);
}
render() {
// Strong typing of store-bound and own props
const {counterValue, counterTitle, increment} = this.props;
return <div onClick={increment}>
${counterTitle}: {counterValue}
</div>;
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
Below are the TypeScript utility types that perform the magic:
///////////////////
// prop-utils.ts
///////////////////
type FunctionType = (...args: any) => any;
type VoidFunc = () => {};
export type ConnectedComponentProps<
StoreActions extends FunctionType = VoidFunc,
StoreProps extends FunctionType = VoidFunc,
OwnProps = {}> = ReturnType<StoreActions> & ReturnType<StoreProps> & OwnProps;
Note, that as expected, when using this component we only get its own properties available and checked:
///////////////////
// Dashboard.ts
///////////////////
import * as React from 'react';
import CounterConnected from "./Counter";
export const Dashboard: React.FunctionComponent = () => {
// Only own props are available and checked
return <CounterConnected counterTitle={'Counter'} />
};
Enum Keys in Object Literal
TypeScript enums are oftentimes used as mapped data keys, as in the example below – an imaginary SPA’s routes having a mapping route (enum key) → route entry.
The two utility types below are used to enforce using only the enum keys in our mapping – either all of them (type RoutesMap) or just some of them (RoutesMapPartial).
type RoutesMap<R> = {[key in keyof typeof AppRoutes]: R };
type PartialRoutesMap<R> = Partial<RoutesMap<R>>;
const enum AppRoutes {
HOME = 'HOME',
LOGIN = 'LOGIN',
DASHBOARD = 'DASHBOARD',
}
interface RouteEntry {
path: string;
}
// All enum keys are required
export const allRoutes: RoutesMap<RouteEntry> = {
[AppRoutes.HOME]: { path: '/home' },
[AppRoutes.LOGIN]: { path: '/login' },
[AppRoutes.DASHBOARD]: { path: '/dashboard'},
};
// Only some enum keys are required
export const someRoutes: PartialRoutesMap<RouteEntry> = {
[AppRoutes.LOGIN]: { path: '/login' },
};
Thanks for reading through, share if you care 🙂
– Zacky
Leave A Comment