One of the issues with JavaScript is its dynamically-typed nature, meaning that data and variable types are unknown until runtime. This can have many side effects. For example, it can cause confusion within your codebase due to the fact that a variable can be anything.
To solve this issue, Microsoft released TypeScript. Anders Hejlsberg, lead architect of TypeScript, said, “What if we could strengthen JavaScript with the things that are missing for large scale application development, like static typing, classes [and] modules…? That’s what TypeScript is about.”
TypeScript instantly became the most widely used static-typed version of JavaScript. It enabled JavaScript devs to statically type data and variables. Soon enough, it was introduced to React.js, enabling React devs to write their React app in TypeScript.
But this comes at a cost: TypeScript typings and syntaxes can be difficult to keep up with, especially when used with React.
React has many features, such as props, class components, function components, function params, components lifecycle hooks, and member properties. Because typing these within TypeScript isn’t easy, this article aims to serve as a quick reference and learning guide for both beginner and advanced React devs.
With it, you’ll be able to quickly look up best practices and generic TS types in React. Ready? Let’s get started.
TypeScript has a typings folder where it saves files with *.d.ts extension. These files include interfaces that infer what shape a value will take. This is what enables TypeScript to bring data-typing to JavaScript.
An interface describes what a value would look like:
type AppState { propOne: number; propTwo: string }
AppState
describes what the value of its data-type would look like. First, we infer it would be an object that holds properties propOne
and propTwo
, the former being of number type and the latter being a string type. Assigning a boolean type to propOne
would cause TypeScript to throw TypeError.
When we include TypeScript in our React project, every React element has an interface that defines the shape it will take. Let’s start with function component.
Function components are normal functions that return JSX elements in React and are used to create views. Initially, they are stateless components, but with the arrival of React hooks, they can be made stateful and smart/
Defining a React function component takes the React.FunctionComponent
shape:
function App: React.FunctionComponent<> { return ( <> // ... </> ) }
We can also use the shorthand React.FC:
function App: React.FC<> { return ( <> // ... </> ) }
React.FunctionComponent
, or React.FC
, describes explicitly the return type of the function component.
We can type the props definitions in the arrows <>
.
type AppProps = { message: string; age: number; };
AppProps
is the interface the props passed to App
will take, so we can write the App
component below if it would receive props:
type AppProps { message: string; age: number; } function App: React.FC<AppProps>(props: AppProps) { return ( <> // ... </> ) }
We can use ?
to set optional values in the typing:
type AppProps { message: string; age?: number; }
Now, the age property becomes optional. The App
component can be rendered, omitting the age property in its props object.
We can omit the type declaration within the arrows <>
.
function App<{message: string; age: number;}>({message: string; age: number;}: AppProps) { // ... }
Inner functions in the functional component can be typed, like so:
function App<{message: string; age: number;}>({message: string; age: number;}: AppProps) { // ... function clickHandler (val: number) { // ... } return ( <> <button onClick={() => clickHandler(45)} </> ) }
Class components are used to create views in React. They are essentially building blocks. A React app can contain many of them, and they define how a small unit of the UI should appear.
Class components have lifecycle hooks that we can hook into to run custom code at any state of the component’s lifecycle.
They use the React.Component<>
:
class App extends React.Component<> { // ... }
You can provide class components with props and state type parameters:
type AppProps = { message: string; age: number; }; type AppState = { id: number; }; class App extends React.Component<AppProps, AppState> { state: AppState = { id: 0 }; // ... }
The props type AppProps
is inserted between the <>
arrows before the state type AppState
.
If you have arguments for class methods, the arguments can be typed:
class App extends React.Component<AppProps, AppState> { state: AppState = { id: 0 }; clickHandler = (val: number) => { // ... } render() { return ( <> <button onClick={() => this.clickHandler(90)}>Click</button> </> ) } }
defaultProps
typingdefaultProps
is used to define default values for the props arguments passed to React components. defaultProps
is helpful for preventing errors when a supposedly available prop becomes missing during runtime. The values within defaultValues
immediately become the value of the props.
React.Fc
is dropped when including defaultProps
in function components because some edge cases in type inference are still a problem in TypeScript.
This means that props are used like they are normally:
type AppProps = { message: string; age: number } & typeof defaultProps; const defaultProps = { message: "", age: 0 }; function App(props: AppProps) { // ... }; App.defaultProps = defaultProps;
Forms are generally used to collect information in an internal state. They are mostly used in login and registration pages so that information submitted can be collected by the form and sent to the server for processing.
React.FormEvent
is used to generally type events from elements.
class App extends React.Component<> { clickHandler = (e: React.FormEvent<HTMLButtonElement>) => { // ... } changeHandler = (e: React.FormEvent<HTMLInputElement>) => { // ... } render() { return ( <div> <button onClick={this.clickHandler}>Click</button> <input type="text" onChange={this.changeHandler} /> </div> ) } }
Both clickHandler
and changeHandler
arguments e
has the React.FormEvent
type.
We can omit typing the handler’s argument with React.FormEvent
and type the return value of the handlers instead. This is done using the React.ChangeEventHandler
:
class App extends React.Component<> { clickHandler: React.FormEvent<HTMLButtonElement> = (e) => { // ... } changeHandler: React.FormEvent<HTMLInputElement> = (e) => { // ... } render() { return ( <div> <button onClick={this.clickHandler}>Click</button> <input type="text" onChange={this.changeHandler} /> </div> ) } }
React.ChangeEvent<T>
is an event type that can be used to type the event handler argument:
class App extends React.Component<> { clickHandler = (e: React.ChangeEvent<HTMLButtonElement>) => { // ... } changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { // ... } render() { return ( <div> <button onClick={this.clickHandler}>Click</button> <input type="text" onChange={this.changeHandler} /> </div> ) } }
Note that T
is the type of the element that the event is registered on.
This describes the React.ChangeEvent type for the button element. Buttons are instances of the HTMLButtonElement class. Here, T
will be HTMLButtonElement
, and React.ChangeEvent will be React.ChangeEvent
.
This describes the React.ChangeEvent
for all input elements: “text,” “password,” “color,” “button,” “media,” etc. They are all instances of the HTMLInputElement class. For all types=[“text”, “password”, etc]
T
will be HTMLTnputElement
. React.ChangeEvent will be React.ChangeEvent
.
This refers to the React.ChangeEvent for the text area element, and text area elements are instances of the HTMLTextAreaElement.
T
will be HTMLTextAreaElement
.
React.ChangeEvent will be React.ChangeEvent
.
Select element is used to create a dropdown list with options for selection. Select elements are instances of HTMLSelectElement.
T
will be HTMLSelectElement
. React.ChangeEvent will be React.ChangeEvent
.
Form elements collects information from the DOM. Form elements are instances of the HTMLFormElement. T will be HTMLFormElement
. React.ChangeEvent will be React.ChangeEvent
.
Video is used to play videos on the browser. The video element is an instance of the HTMLVideoElement. This describes how to set the React.ChangeEvent in the Form element.
T
will be HTMLVideoElement
.
React.ChangeEvent will be React.ChangeEvent
.
In turn, Audio is used to play audio files (mp3, aac, etc.) in the browser. The audio element is an instance if the HTMLAudioElement.
T
will be HTMLAudioElement
.
React.ChangeEvent will be React.ChangeEvent
.
React.SyntheticEvent
This type is used when you aren’t concerned with the type of event:
class App extends React.Component<> { submitHandler = (e: React.SyntheticEvent) => { // ... } render() { return ( <form onSubmit={this.submitHandler}> ... </form> ) } }
Hooks are supported in Reactv16.8+. They allow us to use state in functional components.
useState
This is used to set state in functional components, and the state is kept alive throughout the lifetime of the component.
const [state, setState] = useState(0)
We can infer state
to be a number and setState
a function that takes numbers.
const [state, setState] = useState<number, (v: boolean) => : void >(0)
If the state takes an object:
type StateObject = { loading: boolean } const [state, setState] = useState<StateObject, (boolean) => : void >({loading: true})
Often times, the useState
initial value is null
. We can use the union type to explicitly declare type, like so:
const [state, setState] = useState<StateObject | null, (boolean) => : void >(null)
This StateObject | null
tells TypeScript that the state can be of StateObject
type or of null
type.
useReducer
useReducer
allows us to maintain state and concurrently dispatch actions to the store from our functional components. It takes an initial state and a reducer function:
const intialState = { loggedIn: false } function reducer(state, action) { // ... } const [state, dispatch ] = useReducer(initialState, reducer)
We can define a type of the state, then type the reducer function based on the return type. Dispatch can be typed based on the action argument type on the reducer function:
type LogginState = { loggedIn: boolean; }; type ActionType = { type: string; payload: boolean; }; const intialState: LogginState = { loggedIn: false } function reducer(state: LogginState, action: ActionType): LogginState { // ... } const [state, dispatch ] = useReducer<LogginState, (v: ActionType) => : void>(initialState, reducer)
useRef
useRef
allows us to access refs in React nodes and maintain them throughout the lifetime of the component.
null
types are mostly passed to useRef
, so the typing would look like this:
const ref= useRef<HTMLElement | null>(null)
We can be more specific in the type of element we are trying to reference:
// this types useRef to button elements const buttonRef= useRef<HTMLButtonElement | null>(null) // this types useRef to input elements const inputRef= useRef<HTMLInputElement | null>(null)
Also, useRef
can be used on React.Component
:
const inputRef= useRef<React.Component | null>(null)
useContext allows us to create and maintain contexts in our functional components throughout their lifetime. The contexts are created using the React Context API.
Here’s how to use useContext
and createContext
to create a context:
const authContext = createContext({isAuth: true}) const context = useContext(authContext)
The default value type of createContext
will form the basis of its argument type:
type AuthType = { isAuth: boolean; }; const authContext = createContext<AuthType>({isAuth: true})
Also, useContext
argument type will be AuthType
:
const context = useContext<AuthType>(authContext)
Hopefully you’ve found this guide informative and helpful. You can also read more about React TypeScript type inference in this Github repo.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.