The project's README from github.com/piotrwitek/react-redux-typescript-guide. As one markdown file: README.md.
"This guide is a living compendium documenting the most important patterns and recipes on how to use React (and its Ecosystem) in a functional style using TypeScript. It will help you make your code completely type-safe while focusing on inferring the types from implementation so there is less noise coming from excessive type annotations and it's easier to write and maintain correct types in the long run."
Found it useful? Want more updates?
Show your support by giving a :star:
:tada: Now updated to support TypeScript v4.6 :tada:
:rocket: _Updated to [email protected] :rocket:
--strict flag) without losing type information downstream through all the layers of our application (e.g. no type assertions or hacking with any type)Check out our Playground Project located in the /playground folder. It contains all source files of the code examples found in the guide. They are all tested with the most recent version of TypeScript and 3rd party type-definitions (like @types/react or @types/react-redux) to ensure the examples are up-to-date and not broken with updated definitions (It's based on create-react-app --typescript).
Playground project was created so that you can simply clone the repository locally and immediately play around with all the component patterns found in the guide. It will help you to learn all the examples from this guide in a real project environment without the need to create complicated environment setup by yourself.
You can help make this project better by contributing. If you're planning to contribute please make sure to check our contributing guide: CONTRIBUTING.md
You can also help by funding issues. Issues like bug fixes or feature requests can be very quickly resolved when funded through the IssueHunt platform.
I highly recommend to add a bounty to the issue that you're waiting for to increase priority and attract contributors willing to work on it.
🌟 - New or updated section
React.FC<Props> | React.FunctionComponent<Props>React.Component<Props, State>React.ComponentType<Props>React.ComponentProps<typeof XXX>React.ReactElement | JSX.ElementReact.ReactNodeReact.CSSPropertiesReact.XXXHTMLAttributes<HTMLXXXElement>React.ReactEventHandler<HTMLXXXElement>React.XXXEvent<HTMLXXXElement>npm i -D @types/react @types/react-dom @types/react-redux
"react" - @types/react
"react-dom" - @types/react-dom
"redux" - (types included with npm package)*
"react-redux" - @types/react-redux
*NB: Guide is based on types for Redux >= v4.x.x.
React.FC<Props> | React.FunctionComponent<Props>Type representing a functional component
const MyComponent: React.FC<Props> = ...
React.Component<Props, State>Type representing a class component
class MyComponent extends React.Component<Props, State> { ...
React.ComponentType<Props>Type representing union of (React.FC<Props> | React.Component<Props>) - used in HOC
const withState = <P extends WrappedComponentProps>(
WrappedComponent: React.ComponentType<P>,
) => { ...
React.ComponentProps<typeof XXX>Gets Props type of a specified component XXX (WARNING: does not work with statically declared default props and generic props)
type MyComponentProps = React.ComponentProps<typeof MyComponent>;
React.ReactElement | JSX.ElementType representing a concept of React Element - representation of a native DOM component (e.g. <div />), or a user-defined composite component (e.g. <MyComponent />)
const elementOnly: React.ReactElement = <div /> || <MyComponent />;
React.ReactNodeType representing any possible type of React node (basically ReactElement (including Fragments and Portals) + primitive JS types)
const elementOrPrimitive: React.ReactNode = 'string' || 0 || false || null || undefined || <div /> || <MyComponent />;
const Component = ({ children: React.ReactNode }) => ...
React.CSSPropertiesType representing style object in JSX - for css-in-js styles
const styles: React.CSSProperties = { flexDirection: 'row', ...
const element = <div style={styles} ...
React.XXXHTMLAttributes<HTMLXXXElement>Type representing HTML attributes of specified HTML Element - for extending HTML Elements
const Input: React.FC<Props & React.InputHTMLAttributes<HTMLInputElement>> = props => { ... }
<Input about={...} accept={...} alt={...} ... />
React.ReactEventHandler<HTMLXXXElement>Type representing generic event handler - for declaring event handlers
const handleChange: React.ReactEventHandler<HTMLInputElement> = (ev) => { ... }
<input onChange={handleChange} ... />
React.XXXEvent<HTMLXXXElement>Type representing more specific event. Some common event examples: ChangeEvent, FormEvent, FocusEvent, KeyboardEvent, MouseEvent, DragEvent, PointerEvent, WheelEvent, TouchEvent.
const handleChange = (ev: React.MouseEvent<HTMLDivElement>) => { ... }
<div onMouseMove={handleChange} ... />
In code above React.MouseEvent<HTMLDivElement> is type of mouse event, and this event happened on HTMLDivElement
import * as React from 'react';
type Props = {
label: string;
count: number;
onIncrement: () => void;
};
export const FCCounter: React.FC<Props> = props => {
const { label, count, onIncrement } = props;
const handleIncrement = () => {
onIncrement();
};
return (
<div>
<span>
{label}: {count}
</span>
<button type="button" onClick={handleIncrement}>
{`Increment`}
</button>
</div>
);
};
import * as React from 'react';
type Props = {
label: string;
count: number;
onIncrement: () => void;
};
// React.FC is unaplicable here due not working properly with default props
// https://github.com/facebook/create-react-app/pull/8177
export const FCCounterWithDefaultProps = (props: Props): JSX.Element => {
const { label, count, onIncrement } = props;
const handleIncrement = () => {
onIncrement();
};
return (
<div>
<span>
{label}: {count}
</span>
<button type="button" onClick={handleIncrement}>
{`Increment`}
</button>
</div>
);
};
FCCounterWithDefaultProps.defaultProps = { count: 5 };
import * as React from 'react';
type Props = React.PropsWithChildren<{
className?: string;
style?: React.CSSProperties;
}>;
export const FCSpreadAttributes: React.FC<Props> = (props) => {
const { children, ...restProps } = props;
return <div {...restProps}>{children}</div>;
};
import * as React from 'react';
type Props = {
label: string;
};
type State = {
count: number;
};
export class ClassCounter extends React.Component<Props, State> {
readonly state: State = {
count: 0,
};
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
const { handleIncrement } = this;
const { label } = this.props;
const { count } = this.state;
return (
<div>
<span>
{label}: {count}
</span>
<button type="button" onClick={handleIncrement}>
{`Increment`}
</button>
</div>
);
}
}
import * as React from 'react';
type Props = {
label: string;
initialCount: number;
};
type State = {
count: number;
};
export class ClassCounterWithDefaultProps extends React.Component<
Props,
State
> {
static defaultProps = {
initialCount: 0,
};
readonly state: State = {
count: this.props.initialCount,
};
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
const { handleIncrement } = this;
const { label } = this.props;
const { count } = this.state;
return (
<div>
<span>
{label}: {count}
</span>
<button type="button" onClick={handleIncrement}>
{`Increment`}
</button>
</div>
);
}
}
import * as React from 'react';
export interface GenericListProps<T> {
items: T[];
itemRenderer: (item: T) => JSX.Element;
}
export class GenericList<T> extends React.Component<GenericListProps<T>, {}> {
render() {
const { items, itemRenderer } = this.props;
return (
<div>
{items.map(itemRenderer)}
</div>
);
}
}
import * as React from 'react';
type Props = { initialCount: number };
export default function Counter({initialCount}: Props) {
const [count, setCount] = React.useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
import * as React from 'react';
import ThemeContext from '../context/theme-context';
type Props = {};
export default function ThemeToggleButton(props: Props) {
const { theme, toggleTheme } = React.useContext(ThemeContext);
return (
<button onClick={toggleTheme} style={theme} >
Toggle Theme
</button>
);
}
import * as React from 'react';
interface State {
count: number;
}
type Action = { type: 'reset' } | { type: 'increment' } | { type: 'decrement' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error();
}
}
interface CounterProps {
initialCount: number;
}
function Counter({ initialCount }: CounterProps) {
const [state, dispatch] = React.useReducer(reducer, {
count: initialCount,
});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
export default Counter;