This content originally appeared on Telerik Blogs and was authored by Peter Mbanugo
Considering adopting TypeScript for your React app? Learn what to watch for before you make the switch.
TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at scale. It has gained wide adoption and I’ve seen many React developers switch to using TypeScript for their React applications.
While there’s wide adoption of TypeScript, there are still a lot of teams that don’t use TypeScript but are interested in adopting TypeScript for their React projects. These are some of the concerns or questions that these teams or developers wonder about:
- How to define types for React components, props and state
- How to use React hooks and event handlers in TypeScript
- Setting up a TypeScript project
- Migrating existing applications to TypeScript
I will address the first three of these questions in this post, and in a later post, we will see how to migrate a React application to TypeScript.
Prerequisites
For a better understanding of this post, you should be familiar with React and have basic knowledge of TypeScript. I will show you how to create a new project, define components, and work with hooks and functions in a React application.
Let’s get started!
Setting Up a TypeScript Project
The first question that comes up when I discuss TypeScript with a React dev is: “How do I create a React TypeScript Project?” You most likely use a tool to create a new React project, for example Create React App (CRA), which allows you to set up a modern web app by running the command npx create-react-app react-app
. Frameworks like Next.js, Remix and Gatsby have their own CLI that enables
you to set up a TypeScript-base app.
For CRA, you use the command npx create-react-app react-app --template typescript
to create a TypeScript app. For Next.js it’s npx create-next-app --typescript
. Whichever framework/tool you choose to use, check the documentation
for how to use TypeScript.
Using these CLIs, you don’t have to configure or specify any TypeScript configuration—they’re automatically generated. You can tweak the generated tsconfig.json file to suit your needs. You can add some community recommended config via
npm, for example using @tsconfig/esm
. You can see more info about the different tsconfig extensions at github.com/tsconfig/bases. I use the Node 16
and Node 17 configuration when building Node.js apps, but using a tool like create-next-app
I usually don’t need additional configuration.
Using ESLint
You can add TypeScript support to ESLint using the packages @typescript-eslint/parser
and @typescript-eslint/eslint-plugin
. The former is used to parse TypeScript code to a format that is understandable by ESLint, while the latter
provides TypeScript-specific linting rules. Add them to your project using the command below:
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
Then add the following config to your .eslintrc.js
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest", // Allows the use of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
},
extends: ["plugin:@typescript-eslint/recommended"], // Uses the linting rules from @typescript-eslint/eslint-plugin
env: {
node: true, // Enable Node.js global variables
},
};
Check the documentation at github.com/typescript-eslint/typescript-eslint to learn more about TypeScript + ESLint.
JSX & TypeScript
You can name your files with the .tsx extension when working with TypeScript. You don’t need to configure anything extra if you used a tool to bootstrap the project. Otherwise, TypeScript has a jsx
configuration option that controls how JSX constructs are emitted in JavaScript files. This only affects the output of JS files that end in .tsx
.
Learn more about JSX under the hood.
Function Component
Function components can be written as regular functions that accept arguments and return a React element. For example:
export const HelloWorld = () => <h1>Hello World!</h1>;
In this example, the return type is inferred by TypeScript. You can choose to specify the return type so that an error is raised if you accidentally return a different type. For example:
export const HelloWorld = (): JSX.Element => <h1>Hello World!</h1>;
Typing Props
You can define the type for the function argument either inline or as a separate type declaration.
Here’s an example with the prop type defined inline:
export const HelloWorld = ({ message }: { message?: string }) => (
<h1>Hello {message ?? "World"}!</h1>
);
You can declare a type using either the type
alias or as an interface. My personal preference is to use interface
until I have a need to use the type
alias. But I’ve worked with teams that prefer to use the
type
alias in all cases and I go with their approach. You can read more about the differences in the TypeScript docs, and explore an example in the TypeScript playground.
Children Props
If the component accepts other React components as props, you can include the children
property and set its type to React.ReactNode
.
interface HelloProps {
message: string;
children?: React.ReactNode;
}
export const HelloWorld = ({ message, children }: HelloProps) => (
<>
<h1>Hello {message ?? "World"}!</h1>
<div>{children}</div>
</>
);
What About CSS Styles?
You may ask, “How would I specify the type of the style
attribute of an element?” You can do that using the React.CSSProperties
type.
Here’s an example:
interface HelloProps {
message: string;
style?: React.CSSProperties;
}
export const HelloWorld = ({ message, style }: HelloProps) => (
<>
<h1 style={style}>Hello {message ?? "World"}!</h1>
</>
);
Hooks
React hooks have been available since version 16.8, and many would encourage you to use functional components with hooks, rather than class components. I will stick to functional components in this post. Check out this cheatsheet if you want to know about using TypeScript with React class components.
useState
I won’t go into details about every React hook available. I’ll rather pick a few and show you how to work with hooks. Let’s start with the useState
hook.
You can call useState hook and specify the type for the state as follows:
const [message, setState] = useState<string>("hello world");
By specifying the type, only string values will be allowed when calling the hook or using the setState
function. However, type inference works really well in this case. You can rewrite the example above to use inference using the code below:
const [message, setState] = useState("hello world");
// inferred type for `message`: string
// setState only accepts string a value
Sometimes you want to call useState
without any argument (or use a null value), and later on update the state with the actual value. In this case, you would need to explicitly specify the type using a union type. For example:
const [message, setState] = useState<string | undefined>();
// `message` type: string | undefined
// setState only accepts string a value or undefined
When you use type inference and you need the inferred type, you can use the typeof
operator to get the type. For example:
const [state, setState] = React.useState({
id: 104,
name: "Joe Lars",
passportNumber: "A093JK23",
}); // the inferred type is {id: number, name: string, passportNumber: string}
const sendMessage = (user: typeof state) => {
findPassport(user.passportNumber); // a function using `user`
};
useContext / useMemo / useCallback
Other hooks also have type inference support. Take useContext
for example—it can infer the type based on the object that is passed in as an argument.
type Color = "grey" | "black" | "pink";
const ColorContext = createContext<Color>("pink");
const Header = () => {
const colour = useContext(ColorContext);
return <div style={{ colour }}>The legend of Kora</div>;
};
useMemo
and useCallback
also have their types inferred from the values that they
return.
Events
The types for events are inferred through contextual typing if the event handler is defined inline.
<button
onClick={(e) => {
// `e` will be correctly typed automatically!
}}
/>
If you define the handler function separately, then you will need to specify the type. The code below is an example of using a text input.
const Input = () => {
const [state, setState] = useState("");
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setState(e.target.value);
};
const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
e.preventDefault();
};
return (
<input
type="text"
value={state}
onChange={handleChange}
onPaste={handlePaste}
/>
);
};
The code snippet has two event handler functions handleChange
and handlePaste
. The event object e
for handleChange
uses the ChangeEvent generic type, while handlePaste
uses
ClipboardEvent generic type. Every event has a distinct type. The cut
, copy
and paste
events uses the ClipboardEvent. You can look up the list of event types, and more
about events in the React TypeScript Cheatsheet.
Conclusion
In this post, I showed you how to set up a new React project using TypeScript, and how to work with functional components, hooks and event handlers using TypeScript. Stay tuned for a later post walking you through migrating a React project to TypeScript.
This content originally appeared on Telerik Blogs and was authored by Peter Mbanugo
Peter Mbanugo | Sciencx (2022-11-15T13:58:01+00:00) Using TypeScript in React. Retrieved from https://www.scien.cx/2022/11/15/using-typescript-in-react/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.