Open In App

Building Reusable Components in React with TypeScript Generics

Last Updated : 09 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In modern web development, reusability is necessary to making capable, and modifiable applications. TypeScript, with its robust typing system, and React, with its component-based architecture, give dynamic tools to reach reusability. One of these tools is Generics. Generics permit developers to make reusable components, hooks, and functions that can work with any data type while confirming type safety.

Building-reusable-components
Building Reusable Components in React with TypeScript Generics

These are the following topics that we are going to discuss:

Merits of Generics

  • Improved Type Safety: Generics provide type safety because it assures that the types you use in your code are trustworthy. It helps in detecting type-related errors at compile time instead of run-time! As an example, using the fact that if you are creating some sort of generic function that works with a list of items then TypeScript makes sure you pass the correct type or types few different situations as its effects have to be passed down and reduce error margins at run time thus increasing correctness throughout your code.
  • Augmented Code Reusability: Generics permit to write code that can manage multiple data types without breaking down type safety. This directs to more reusable components and functions. As an instance, a generic 'Array' type can be implemented with any sort of elements, such as numbers, strings, or custom objects, making it adjustable for multiple instances. In React, this shows you can create components and hooks that work with any data type, thus it lowers repetition and advances stability across your codebase.
  • Sustainability: One of the major merits of using generics is the reduction of code duplication, resulting in more manageable code. When your code is generic, it is simpler to manage because any modification you require to make only has to be made in one place instead of multiple versions of similar code. Hence, generics make your code more clear, as the type constraints define how the code should be used.
  • Design Versatile Component: In React, generics permit us to create reusable components. As an instance, a generic 'List' component can be designed to render varied types of data without being rewritten for each type. By using generics, you can define the component's props and state in a way that fits distinct types, allowing you to manage varied data structures with a solo component.

Exploring Generics in TypeScript

Generics in TypeScript let developers make dynamic, reusable components or functions that can manage distinct data types while maintaining strict type safety. This will be managed by defining a generic type that acts as a substitute for a specific type, permitting the same piece of code to work with multiple types of data.

Setting Up a TypeScript Project

  • Open your terminal or command prompt and type this command
cd my-project
  • Write this command and install TypeScript and types for Node.js
npm install typescript @types/node --save-dev
  • This command generates a 'tsconfig.json' file, which is used to configure TypeScript settings
npx tsc --init
  • Compile the TypeScript code into javascript by writing this command
npx tsc

Basic Syntax of Generics

A generic function or component in TypeScript uses angle brackets (<>) to denote the generic type parameter. For an example:

function identity<T>(value: T): T {
return value;
}

Where, 'T' is a generic type parameter, and identity is a function that receives a parameter of type 'T' and returns a value of the same type.

Folder Structure:

Screenshot-2024-08-25-072326
Project structure

Example: A easy Generic Function that returns the argument passed to it.

HTML
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  
<head>
    <meta charset="UTF-8">
    <meta name="viewport" 
          content="width=device-width, initial-scale=1.0">
    <title>TypeScript Generic Function Example</title>
</head>
  
<body>
    <script src="app.js"></script>
</body>
  
</html>
JavaScript
//app.js

function identity(arg) {
    return arg;
}
let stringIdentity = identity("Hello GFG from TypeScript.");
let numberIdentity = identity(581);
console.log(stringIdentity);
console.log(numberIdentity);
JavaScript
//app.ts

function identity<T>(arg: T): T {
    return arg;
}

const stringIdentity = identity("Hello GFG from TypeScript.");
const numberIdentity = identity(581);

console.log(stringIdentity); 
console.log(numberIdentity); 

Explanation:

  • This given code defines a generic function identity that returns the exact value passed to it. It then calls this function with a string and a number, logging the results to the console.
  • 'T' is a generic type parameter which acts as a placeholder for the type that will be provided when the function is called.
  • The 'identity' function takes a single argument 'arg' of type 'T' and returns it.
  • The function is apply with a string and a number, showing how it can manage multiple types.

Output:

genericfunctions
Output

Using Generics in React Components

Generics in Typescript will be more important when we are used in react components, this will permit developers to make reusable UI elements that can manage multiple data types while keeping strong type safety. By using generics, we can design components that are not tied to a particular data type but can fit to various types as required. Hence, this makes your react components more dynamic or reusable between varied parts of your application.

Setting Up the React Project with TypeScript

  • Open your terminal or command prompt and run the given command to create a new React project using TypeScript.
npx create-react-app react-project --template typescript
  • This command navigate you to the Project Directory
cd react-project

Folder Structure:

folder-structure
Project structure

Example: Let us create a generic ' List ' component that can show a list of items of any type.

JavaScript
//src/components/GenericComponent.tsx

import React from 'react';

// A Generic Component
type Props<T> = {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
};

function GenericComponent<T>({ items, renderItem }: Props<T>): JSX.Element {
  return <div>{items.map(renderItem)}</div>;
}

export default GenericComponent;
JavaScript
//This file demonstrates the usage of the 'genericComponent'
//src/App.tsx

import React from 'react';
import GenericComponent from './components/GenericComponent';

const App = () => (
  <GenericComponent
    items={[101, 102, 103]}
    renderItem={(item) => <div key={item}>{item}</div>}
  />
);

export default App;
JavaScript
// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

reportWebVitals();

The given generic component 'GenericComponent.tsx' file accepts an array of items and renderItem function as props. The 'renderItem' function is responsible to render every item in the array. In the 'App.tsx' file uses the 'GenericComponent.tsx' component to render a list of numbers (101, 102, and 103) and display. Each number is moved to the 'renderItem' function to build a div element. The 'index.tsx' file is the access point of the React application. It renders the 'App.tsx' component within the root element of the HTML.

Run the application by this using command:

npm start

Output:

output
Output

Building Reusable Hooks with Generics

React Hooks are a core feature in React that permit you to reuse stateful logic across your components. By applying Hooks, you can handle state and side effects in function components, which were previously handled by class components. When you use generics inside hooks, you make them more dynamic and letting the same hook to work with varied data types while keeping type safety. Let us immerse into forming a reusable hook that operates an array of items using a generics.

Folder Structure:


Screenshot-2024-08-24-193033
Project tsructure

Updated Dependencies:

//Package.json File

"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.105",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},

Example: This example shows the Building Reusable Hooks with Generics.

JavaScript
//src/hooks/useArray.ts

import { useState } from 'react';

function useArray<T>(initialArray: T[]) {
    const [array, setArray] = useState<T[]>(initialArray);

    const add = (item: T) => setArray([...array, item]);
    const remove = (index: number) => setArray(array.filter((_, i) => i !== index));
    const clear = () => setArray([]);

    return { array, add, remove, clear };
}

export default useArray;
JavaScript
// src/App.tsx

import React from 'react';
import useArray from './hooks/useArray';

const App: React.FC = () => {
    const { array, add, remove, clear } = useArray<number>([5, 6, 7]);

    return (
        <div>
            <h5>Array Management with Custom Hook</h5>
            <ul>
                {array.map((item, index) => (
                    <li key={index}>{item}</li>
                ))}
            </ul>
            <button onClick={() => add(8)}>add 8</button>
            <button onClick={() => remove(0)}>remove first</button>
            <button onClick={clear}>clear</button>
        </div>
    );
};

export default App;
  • This custom hook 'useArray' permits you to manage an array state with operations such as adding, removing, and clearing items.
  • App.tsx file uses the 'useArray' hook to manage an array of numbers. It shows the array in a list and provides buttons to add a new item, remove the first item, and clear the entire array.

Output:

output
Output

Real-World Example: Building a Type-Safe Form Builder

This project explains you how to create a 'type-safe' form builder using TypeScript and React. This build a form where fields are dynamically handled using generics in TypeScript.

Note: You have to follow the same steps as you followed earlier to create an application.

Folder Structure:

Screenshot-2024-08-30-114341
Project structure

Updated dependencies:

//package.json file
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.106",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},

Example: This example shows the Building a Type-Safe Form Builder.

JavaScript
// src/types/FormValues.ts
export type FormValues = {
    name: string;
    age: number;
  };
  
JavaScript
// src/components/FormField.tsx
import React, { ChangeEvent } from 'react';

type FormFieldProps<T extends Record<string, any>> = {
  name: keyof T;
  value: T[keyof T];
  onChange: (name: keyof T, value: T[keyof T]) => void;
};

function FormField<T extends Record<string, any>>({ name, value, onChange }: FormFieldProps<T>) {
  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    onChange(name, event.target.value as T[keyof T]);
  };

  return (
    <div>
      <label>{String(name)}</label>
      <input type="text" value={String(value)} onChange={handleChange} />
    </div>
  );
}

export default FormField;
JavaScript
// src/components/FormBuilder.tsx
import React, { useState } from 'react';
import FormField from './FormField';
import { FormValues } from '../types/FormValues';

function FormBuilder() {
  const [values, setValues] = useState<FormValues>({
    name: '',
    age: 0,
  });

  const handleChange = (name: keyof FormValues, value: FormValues[keyof FormValues]) => {
    setValues((prevValues) => ({ ...prevValues, [name]: value }));
  };

  return (
    <form>
      <FormField name="name" value={values.name} onChange={handleChange} />
      <FormField name="age" value={values.age} onChange={handleChange} />
    </form>
  );
}

export default FormBuilder;
JavaScript
// src/App.tsx
import React from 'react';
import FormBuilder from './components/FormBuilder';

function App() {
  return (
    <div className="App">
      <FormBuilder />
    </div>
  );
}

export default App;
JavaScript
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);


By using this command run the code:

npm start

Output:

form-builder
Output

Best way of Using Generics in TypeScript and React

  • Initiate with Specific Types and then Generalize: Initiate by applying an function, component, and hook with specific types. This will help in prioritize on the basic feature before adding complexity with generics. Once the feature is executing as assumed, recode the code to use generics. It permits the same logic to work with multiple types, which strengthening reusability.
  • Use Informative Generic Names: Use informative names for your generic types. Rather than using single-letter names such as T, U, or V, prefer using more meaningful names like 'ItemType', 'ResponseType', or 'InputType'. Meaningful names strengthen the readability and make it simpler to know the ambition of the generic type.
  • Keep Generics as minimal as Possible: Avoid making generics more complicated than necessary. If a component and function becomes more complex with multiple generic types, determine if it can be eased or divided into segments. Assure that the use of generics does not make the code difficult to get or read. Thus, the target is to manage reusability with maintainability.
  • Lessens Generics usage: As we know generics improve code reusability, using them too much can cause difficult and confusing to read code. Use generics only when requisite and choose exact types when the type requirements are basic. Unnecessary use of generics can make the codebase difficult to analyze and handle.

Conclusion

Generics in TypeScript or React are robust tools for designing reusable and type-safe components or hooks. By learning this and using generics, you will be able to design more dynamic and modifiable applications. This will not only better code quality but also perfects development capability by providing you to write hooks and components that work with multiple data types.


Next Article

Similar Reads