Reusable components are an important part of React Native development. Instead of writing the same UI code multiple times, we can create a component once and reuse it in different screens.
TypeScript helps us make reusable components safer by defining the types of their props, functions, children, and data.
A reusable component is a component that can be used in multiple places in an application.
For example, a custom Button component can be used on the Login, Register, Profile, and Settings screens.
function Welcome(): React.JSX.Element {
return (
<Text>
Welcome
</Text>
);
}
Props allow a parent component to send information to a child component.
interface UserProps {
name: string;
}
function UserCard(
{ name }: UserProps
): React.JSX.Element {
return (
<Text>{name}</Text>
);
}
An interface can be used to describe the props of a component.
interface ButtonProps {
title: string;
onPress: () => void;
}
Here, title must be a string and onPress
must be a function that does not return a value.
interface ButtonProps {
title: string;
onPress: () => void;
}
function CustomButton(
{ title, onPress }: ButtonProps
): React.JSX.Element {
return (
<Button
title={title}
onPress={onPress}
/>
);
}
<CustomButton
title="Login"
onPress={() =>
console.log("Login")
}
/>
The same component can be used with different titles and functions.
interface UserProps {
name: string;
age: number;
course: string;
}
Multiple properties can be defined inside the same interface.
interface UserProps {
name: string;
age: number;
course: string;
}
function UserCard(
{ name, age, course }: UserProps
): React.JSX.Element {
return (
<View>
<Text>
{name}
</Text>
<Text>
Age: {age}
</Text>
<Text>
Course: {course}
</Text>
</View>
);
}
<UserCard
name="Rahul"
age={20}
course="React Native"
/>
TypeScript checks that all required props have the correct types.
Use ? when a prop is optional.
interface UserProps {
name: string;
age?: number;
}
The age property does not have to be provided.
interface ButtonProps {
title: string;
disabled?: boolean;
}
function CustomButton(
{
title,
disabled = false
}: ButtonProps
): React.JSX.Element {
return (
<Button
title={title}
disabled={disabled}
onPress={() => {}}
/>
);
}
interface ButtonProps {
title: string;
disabled: boolean;
}
Boolean props can be used for states such as disabled, loading, selected, or active.
<CustomButton
title="Save"
disabled={true}
/>
Union types allow a prop to accept more than one specific type.
interface BadgeProps {
status: "active" | "inactive";
}
The status can only be active or inactive.
A reusable component can receive a function as a prop.
interface CardProps {
title: string;
onSelect: () => void;
}
This allows the parent component to decide what should happen when the child component is selected.
interface Student {
id: number;
name: string;
}
interface StudentCardProps {
student: Student;
onSelect: (student: Student) => void;
}
The function must receive a Student object.
interface Student {
id: number;
name: string;
course: string;
}
interface StudentCardProps {
student: Student;
onSelect: (student: Student) => void;
}
function StudentCard(
{
student,
onSelect
}: StudentCardProps
): React.JSX.Element {
return (
<Pressable
onPress={() =>
onSelect(student)
}
>
<Text>
{student.name}
</Text>
<Text>
{student.course}
</Text>
</Pressable>
);
}
interface InputProps {
value: string;
placeholder: string;
onChangeText: (text: string) => void;
}
function CustomInput(
{
value,
placeholder,
onChangeText
}: InputProps
): React.JSX.Element {
return (
<TextInput
value={value}
placeholder={placeholder}
onChangeText={onChangeText}
/>
);
}
A container component can be created to provide common layout or styling.
interface ContainerProps {
children: React.ReactNode;
}
function Container(
{ children }: ContainerProps
): React.JSX.Element {
return (
<View>
{children}
</View>
);
}
When a component accepts nested content, the
children prop can be typed as React.ReactNode.
interface CardProps {
children: React.ReactNode;
}
This allows the component to receive text, elements, and other React content.
interface CardProps {
title: string;
children: React.ReactNode;
}
function CustomCard(
{
title,
children
}: CardProps
): React.JSX.Element {
return (
<View>
<Text>
{title}
</Text>
{children}
</View>
);
}
<CustomCard title="Student">
<Text>
Rahul
</Text>
<Text>
React Native
</Text>
</CustomCard>
The same Card component can be reused with different content.
A reusable component can also accept a style prop.
import {
StyleProp,
ViewStyle
} from "react-native";
interface ContainerProps {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
}
This allows callers to provide additional View styles.
any for all props.?.React.ReactNode for children when appropriate.A reusable component can also maintain its own state when the state belongs to that component.
interface CounterProps {
initialValue?: number;
}
function Counter(
{ initialValue = 0 }: CounterProps
): React.JSX.Element {
const [count, setCount] =
useState<number>(initialValue);
return (
<Button
title={`Count: ${count}`}
onPress={() =>
setCount(count + 1)
}
/>
);
}
Reusable components can receive typed API objects as props.
interface User {
id: number;
name: string;
}
interface UserCardProps {
user: User;
}
function UserCard(
{ user }: UserCardProps
): React.JSX.Element {
return (
<View>
<Text>
{user.name}
</Text>
</View>
);
}
Reusable typed components are especially useful with FlatList.
<FlatList<Student>
data={students}
keyExtractor={(item) =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentCard
student={item}
onSelect={handleStudent}
/>
)}
/>
The StudentCard receives a correctly typed Student object.
import React from "react";
import {
View,
Text,
Pressable,
StyleSheet
} from "react-native";
interface Student {
id: number;
name: string;
course: string;
}
interface StudentCardProps {
student: Student;
onSelect: (student: Student) => void;
}
function StudentCard(
{
student,
onSelect
}: StudentCardProps
): React.JSX.Element {
return (
<Pressable
style={styles.card}
onPress={() =>
onSelect(student)
}
>
<Text style={styles.name}>
{student.name}
</Text>
<Text>
Course: {student.course}
</Text>
</Pressable>
);
}
export default function App(): React.JSX.Element {
const students: Student[] = [
{
id: 1,
name: "Rahul",
course: "React Native"
},
{
id: 2,
name: "Priya",
course: "TypeScript"
}
];
const handleStudent =
(student: Student): void => {
console.log(
"Selected:",
student.name
);
};
return (
<View style={styles.container}>
<Text style={styles.title}>
Students
</Text>
{students.map(
(student: Student) => (
<StudentCard
key={student.id}
student={student}
onSelect={handleStudent}
/>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 15
},
card: {
padding: 15,
marginBottom: 10,
borderWidth: 1,
borderRadius: 8
},
name: {
fontSize: 18,
fontWeight: "bold"
}
});
Reusable components help us avoid duplicate code and create consistent React Native applications. TypeScript makes these components safer by defining their props, callback functions, children, styles, and data.
You should now understand typed component props, optional props, union types, function props, children, style props, reusable inputs, buttons, cards, student components, API data, and FlatList components.
React.ReactNode for component children when appropriate.Question: Which TypeScript type is commonly used for React component children?