Page Content

Tutorials

Can we use JSX in TypeScript? With Examples

JSX in TypeScript

Large and complex JavaScript codebase maintenance is made easier by TypeScript’s interaction with contemporary frontend technologies, especially React, which offers a strong layer of static type checking and improved tools. TypeScript works well with React, which is considered a great option for type safety due to the fact that its basic building parts, or components, are specified and used in a way that permits thorough type checking.

React and JSX

Typically, JavaScript XML (JSX), an XML-like syntax extension that is integrated straight into JavaScript code, is used to construct React views. JSX tokens are converted into regular JavaScript function calls via a transpiler (like the TypeScript Compiler, or TSC), which processes JSX, which is not standard JavaScript. Usually, this transition leads to calls to React in the context of React.createElement.

The reasons for utilising JSX are numerous and include:

  1. Type Checking: This feature enables the display layer to be type tested using the same approach that checks the important JavaScript code.
  2. Context Awareness: This enhances the relationship between the view and the controller by enabling the view to understand the context in which it functions.
  3. Code Reuse: Instead of using poorly written alternatives, developers can reduce HTML upkeep by utilising well-known JavaScript patterns (such Array.prototype.map or ternary operators ?:).

The likelihood of errors is greatly decreased, and user interface maintainability is enhanced, thanks to this combination.

TSX (.tsx extension) for Files Containing JSX

If JSX syntax is used in a TypeScript project, the files must be identified by a special extension so that the compiler is aware that JSX is present.

The file extension dedicated to TypeScript code that contains JSX is .tsx. The way TypeScript and JavaScript interact is reflected in the introduction of .tsx: JSX + the compile-time safety and support layer that TypeScript offers is essentially TSX.

JSX syntax support is enabled when TypeScript comes across a file with the .tsx extension, enabling programmers to create HTML-like structures right inside their typed code.

Configuring tsconfig.json for React

It is necessary to provide certain configuration options in the tsconfig.json file for the TypeScript compiler (TSC) to properly process JSX syntax in .tsx files and convert it into executable JavaScript appropriate for React.

It is the "jsx" option under compilerOptions that is the most important setting. It is advised to use the value "react" when integrating with React for standard web development.

What the JSX transformation outputs depends on the jsx compiler option:

  • “preserve”: Type-checks the JSX but emits the JSX syntax as is (e.g., <div /> remains <div />), typically resulting in a .jsx output file extension. This mode relies on a separate external transpiler (like Babel) to perform the final conversion.
  • “react”: Type-checks the JSX and compiles it into JavaScript calls to React.createElement(). This results in a standard .js output file extension. This is generally the default choice for React applications leveraging TSC for bundling.
  • “react-native”: Type-checks the JSX and preserves it without compiling it, but emits a .js file extension.

A React project’s basic tsconfig.json configuration could resemble this, making sure that the necessary definitions are included and that JSX is converted appropriately:

{
  "compilerOptions": {
    "target": "es5", 
    "module": "commonjs", 
    "esModuleInterop": true, 
    "jsx": "react", 
    "lib": [
      "dom",
      "es6"
    ],
    "sourceMap": true, 
    "moduleResolution": "node", 
    "outDir": "./dist/js"
  },
  "include": [
    "src"
  ],
  "types": [
    "node",
    "react",
    "react-dom"
  ]
}

In this configuration, "jsx": "react" ensures that JSX syntax is emitted as React.createElement calls. Additionally, the "lib": ["dom", "es6"] setting ensures that browser and modern JavaScript APIs are available for type checking. The types option explicitly includes type definitions for react and react-dom, which is necessary for TypeScript to understand the types provided by the React library. Using "esModuleInterop": true is also recommended to support typical React imports without using the wildcard syntax, allowing import React from 'react' instead of import * as React from 'react'.

Integrating with React Components, Handling Props and State

React’s high levels of safety are enforced by TypeScript, which uses interfaces to specify the structure of local state and component attributes (props). In essence, components are classes or methods that generate renderable content (JSX or React.ReactNode) by accepting input (props) and possibly managing internal data (state).

Distinguishing HTML Tags from Components

React and TypeScript use the case of an element’s first letter to determine how to compile it:

  • HTML Tags (lowercase): Tags like <div> or <span> are treated as built-in intrinsic elements. Their attributes are checked against the JSX.IntrinsicElements interface defined in React’s type definitions (react-jsx.d.ts).
  • React Components (PascalCase): Tags like <MyComponent /> are treated as user-defined components (functions or classes). The type checker verifies that the attributes passed match the expected Props defined for that component.

Type-Checking Components

Type-checking is done on the component structure using the generic types that were supplied to the component description.

Function Components

Function components, parameterised by a type that explains their intended properties, are defined using the React.FunctionComponent (also known as React.FC or React.SFC) interface.

Handling Props in Function Components: Using a type alias or interface to create properties is how props are handled in function components. TypeScript makes sure that all necessary properties are present and typed appropriately when the component is instantiated using JSX. It also restricts the providing of unnecessary values.

Handling State in Function Components (Hooks): React and other hooks are used by function components to handle internal state.useState. Typically, TypeScript determines the state type based on the first value passed to useState. For complex or unclear initial values, explicit generic type parameters may be employed.

Code Example: Function Component with Props

Here is an example of a simple function component, Hello, which takes compiler and framework properties:

// src/app/app.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

// Define the shape of the properties (Props)
type HelloProps = { 
    compiler: string; 
    framework: string; 
};

// Define the component using React.FunctionComponent interface and Props type
const Hello: React.FunctionComponent<HelloProps> = (props) => {
    return (
        <div>
            <div>{props.compiler}</div>
            <div>{props.framework}</div>
        </div>
    );
}

ReactDOM.render(
    <Hello compiler="TypeScript" framework="React" />,
    document.getElementById("root")
);

Output: This code segment will generate an HTML structure that shows the props contents when it is presented to the DOM element with id="root".

<div id="root">
  <div>TypeScript</div>
  <div>React</div>
</div>

TypeScript ensures that the component is used properly by generating a compile-time error if the developer attempts to give a prop of the incorrect type or omit a necessary prop (compiler or framework).

Class Components

React.Component base class, which is a generic class that takes two type arguments, Props and State, respectively (React.Component<Props, State>), is extended by class components.

Handling Props in Class Components: Props, the first generic argument, defines the structure of props. This.props is used internally to access these props. React’s type definitions enforce the immutability principle of props by marking them as readonly and blocking direct mutation (this.props.foo = 123; results in an error).

Handling State in Class Components: The second generic input, State, defines the internal state structure of the component. To access the state, use this.state. This.setState() API is required to handle state changes, and TypeScript enforces this restriction; a type error will occur if state is directly assigned (this.state.baz = 456;).

Code Example: Class Component with Props and State

To utilize static type checking effectively, React’s createClass style should be converted to an ES6 Class.

// Define interfaces for Props and State
interface MyProps {
    foo: string;
}

interface MyState {
    count: number;
}

class MyComponent extends React.Component<MyProps, MyState> {
    // Initialize state (using property initializer)
    state: MyState = {
        count: 0
    };

    render() {
        // Accessing props (immutable/readonly)
        const displayFoo = this.props.foo;
        // Accessing state
        const currentCount = this.state.count;
        
        return (
            <div>
                <h1>Prop: {displayFoo}</h1>
                <p>State Count: {currentCount}</p>
            </div>
        );
    }

    // Example of mutating state correctly
    incrementCount = () => {
        // TypeScript enforces use of setState, not direct assignment
        this.setState({ count: this.state.count + 1 });
    }

    // Attempting illegal mutation will result in errors:
    someMethod() {
        // this.props.foo = "new value"; // ERROR: props are immutable
        // this.state.count = 99;      // ERROR: must use this.setState
    }
}

Explicit Props and State interfaces are defined and passed as generics to React.Component via TypeScript, which guarantees consistent component usage and minimises errors related to misspelt props, missing necessary data, or improper state manipulation. When TypeScript is incorporated into React frontend development, the main advantage is that the compiler can detect these issues early on instead than during runtime.

Index