3

Using React components written in JavaScript within TypeScript code

 3 years ago
source link: https://wanago.io/2021/04/12/react-components-in-javascript-typescript-code/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Using React components written in JavaScript within TypeScript code

JavaScript React TypeScript

April 12, 2021

TypeScript is getting more and more popular. Therefore, it is a great time to start using it if you haven’t already. Chances are, our project has been developed for quite some time. If that’s the case, the best approach could be to introduce TypeScript to the codebase gradually. We might encounter a case where we would like to use React components written in JavaScript within our TypeScript code.

In this article, we look into how to create TypeScript declaration files that allow us to do that. We also learn how we can create interfaces from React prop types.

Declaration files

We can distinguish a type declaration file by the .d.ts filename extension. Such files’ job is to hold the declarations of our variables and functions without their implementations.

To understand the type declaration files better, let’s create a simple function in JavaScript.

sum.js
function sum(firstNumber, secondNumber) {
  return firstNumber + secondNumber;
export default sum;

We can see that we have an exported function above. Unfortunately, it does not include any types. To deal with that, let’s create a declaration file. The simplest way to do that would be to create a .d.ts in the same directory.

sum.d.ts
function sum(firstNumber: number, secondNumber: number): number;
export default sum;

Since we’ve used the default export in our sum.js file, we also need to use the default export in sum.d.ts.

Above, we declare the function along with its arguments and the return type. The implementation is still in the JavaScript file, so we don’t need to repeat it in the .d.ts file.

Thanks to doing the above, when we import the sum function in our TypeScript code, it is fully type-safe.

index.ts
import sum from './utilities/sum';
console.log(sum('1', 2));

Argument of type ‘”1″‘ is not assignable to parameter of type ‘number’.

We can do a similar thing with variables instead of functions. Although, keep in mind that TypeScript capable of analyzing the JavaScript files to some extent.

constants.js
export const HEADER_HEIGHT = '100px';
export const FOOTER_HEIGHT = '60px';
index.ts
import sum from './utilities/sum';
import { FOOTER_HEIGHT, HEADER_HEIGHT } from './utilities/constants';
console.log(sum(HEADER_HEIGHT, FOOTER_HEIGHT));

Argument of type ‘”100px”‘ is not assignable to parameter of type ‘number’.

Even though we didn’t create a declaration file for the constants.js file, TypeScript properly assigned the HEADER_HEIGHT and FOOTER_HEIGHT constants with the literal types.

Declaring modules

TypeScript is definitely popular. Therefore, all commonly used libraries should have TypeScript declarations. Even if that’s the case, you might need to use a library that does not have the declarations for some reason. For example, it might be a private library developed by your organization.

A common use case is importing image files. Webpack usually takes care of it under the hood, for example, with the file-loader. Still, TypeScript does not recognize image files as modules.

App.tsx
import React from 'react';
import logoUrl from './logo.png';
export const App = () => (
  <div>
    <img src={logoUrl} alt="logo" />
  </div>

Cannot find module ‘./logo.png’.

We can easily deal with that by creating a file with a module declaration.

png.d.ts
declare module '*.png' {
  const url: string;
  export default url;

Above, we declare that all files with the .png extension export a URL. We could also specify a third-party library in such a way, for example.

Creating declaration files for React components

If we are introducing TypeScript to an organization, chances are there are quite a few React components already in place. To provide an example, let’s create a simple input component in JavaScript.

Input/index.tsx
import React from 'react';
import PropTypes from 'prop-types';
const Input = ({
  label,
  onChange,
  name,
  type = 'text',
}) => {
  return (
    <div>
        label && <label>{label}</label>
      <input
        name={name}
        type={type}
        onChange={onChange}
      />
    </div>
Input.propTypes = {
  label: PropTypes.string,
  onChange: PropTypes.func,
  name: PropTypes.string.isRequired,
  type: PropTypes.string,
export default Input;

If we try to import the Input component in TypeScript as it is, we do get some type safety, but not much.

import React, { ComponentProps } from 'react';
import Input from './components/shared/Input';
type InputProps = ComponentProps<typeof Input>;
    label: any;
    onChange: any;
    name: any;
    type?: string | undefined;

ComponentProps is a utility shipped with React that can extract the types of the props of a component

Above, we can see that TypeScript can understand our Input component to some extent. Even though some props such as the label are not required, this is not shown in the above types. The type prop is described somewhat correctly because we’ve provided a default value. Although, we can improve it further.

Input/index.d.ts
import { ChangeEvent, FunctionComponent } from 'react';
type InputType = 'text' | 'checkbox' | 'email' | 'date' | 'password';
interface Props {
  name: string;
  onChange: (event: ChangeEvent<HTMLInputElement>) => void;
  type?: InputType;
  label?: string;
const Input: FunctionComponent<Props>;
export default Input;

If you are writing class components, use the ComponentClass type imported from React instead of FunctionComponent.

You can see that we are free to import additional types in our declaration file above. Creating such declarations results in type-safe React components, even if they weren’t created with TypeScript.

Creating interfaces from prop types

Above, we analyze a JavaScript React component and create a TypeScript interface from scratch. Doing that has the best chance of producing good results, but it takes quite a bit of time. Chances are, the React component we are trying to use already has prop types defined.

To create a TypeScript interface from prop types, we need the InferProps utility.

Input/index.d.ts
import { FunctionComponent } from 'react';
import PropTypes, { InferProps } from 'prop-types';
const propTypes = {
  label: PropTypes.string,
  onChange: PropTypes.func,
  name: PropTypes.string.isRequired,
  type: PropTypes.string,
type InputProps = InferProps<typeof propTypes>
const Input: FunctionComponent<InputProps>;
export default Input;

Doing the above might get us some type-safety. Let’s inspect what props the above declaration produces:

  label?: string | null;
  onChange: (...args: any[]) => any) | null | undefined;
  name: string;
  type?: string | null,

We can see that we can use prop types to generate valid TypeScript interfaces. Unfortunately, they miss some of the essential details, such as the arguments of the onChange function.

Summary

In this article, we’ve looked into how to use JavaScript React components within our TypeScript code. To do that, we’ve learned the basic principles of writing declaration files. We’ve also learned how to create declarations for React components both from scratch and using prop types. Finally, we’ve compared both approaches, and it looks like it might be a better idea to write TypeScript interfaces from scratch in a lot of the cases.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK