Guide for building React Native components using the "Thinking in React" methodology. Adapts React component design principles for mobile development with NativeWind styling, TypeScript, and React Native-specific patterns. Use when designing new features, breaking down UI into components, planning component hierarchy, or structuring React Native applications.
Adapts the "Thinking in React" methodology for React Native mobile development. Follow these five steps to build well-structured, maintainable React Native components.
Start by identifying components and subcomponents in your design. Consider:
FilterableProductList (screen container)
├── SearchBar (search input + filter checkbox)
└── ProductList (scrollable list)
├── ProductCategoryHeader (category title)
└── ProductItem (individual product row)
Build components that render UI without interactivity first. Use props to pass data down.
| Web HTML | React Native | Notes |
|---|---|---|
<div> | <View> | Container component |
<span>, <p> | <Text> | Text content |
<input> | <TextInput> | Text input |
<button> | <Pressable> or <TouchableOpacity> | Button/clickable |
<ul>, <ol> | <FlatList> or <ScrollView> | Lists |
<table> | <FlatList> with custom renderItem | Tables (rare in mobile) |
<img> | <Image> | Images |
import { View, Text, TextInput, Pressable, FlatList } from 'react-native';
interface Product {
category: string;
price: string;
stocked: boolean;
name: string;
}
interface ProductCategoryHeaderProps {
category: string;
}
const ProductCategoryHeader = ({ category }: ProductCategoryHeaderProps) => {
return (
<View className="bg-gray-100 px-4 py-2">
<Text className="font-bold text-lg">{category}</Text>
</View>
);
};
interface ProductItemProps {
product: Product;
}
const ProductItem = ({ product }: ProductItemProps) => {
const nameStyle = product.stocked
? "text-black"
: "text-red-500";
return (
<View className="flex-row justify-between px-4 py-3 border-b border-gray-200">
<Text className={nameStyle}>{product.name}</Text>
<Text className="text-gray-600">{product.price}</Text>
</View>
);
};
interface ProductListProps {
products: Product[];
}
const ProductList = ({ products }: ProductListProps) => {
const renderItem = ({ item }: { item: Product }) => {
// Group by category logic here
return <ProductItem product={item} />;
};
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={(item) => item.name}
className="flex-1"
/>
);
};
interface SearchBarProps {
// Props will be added in Step 4
}
const SearchBar = ({}: SearchBarProps) => {
return (
<View className="p-4 bg-white border-b border-gray-200">
<TextInput
placeholder="Search..."
className="border border-gray-300 rounded-lg px-4 py-2 mb-3"
/>
<View className="flex-row items-center">
<Pressable className="mr-2">
{/* Checkbox implementation */}
</Pressable>
<Text>Only show products in stock</Text>
</View>
</View>
);
};
interface FilterableProductListProps {
products: Product[];
}
const FilterableProductList = ({ products }: FilterableProductListProps) => {
return (
<View className="flex-1 bg-white">
<SearchBar />
<ProductList products={products} />
</View>
);
};
className prop for stylingIdentify what data changes over time. State should be minimal and DRY (Don't Repeat Yourself).
For each piece of data, ask:
For a searchable product list:
Result: Only filterText and inStockOnly are state.
Determine which component owns each piece of state.
For each piece of state:
FilterableProductList (owns state)
├── SearchBar (displays state, needs to update it)
└── ProductList (uses state to filter)
Both SearchBar and ProductList need the filter state, so it lives in FilterableProductList.
import { useState } from 'react';
const FilterableProductList = ({ products }: FilterableProductListProps) => {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);
return (
<View className="flex-1 bg-white">
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
/>
<ProductList
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</View>
);
};
Child components need to update parent state. Pass callback functions down as props.
// Parent passes state setters as callbacks
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly}
/>
// Child calls callbacks to update parent state
interface SearchBarProps {
filterText: string;
inStockOnly: boolean;
onFilterTextChange: (text: string) => void;
onInStockOnlyChange: (value: boolean) => void;
}
const SearchBar = ({
filterText,
inStockOnly,
onFilterTextChange,
onInStockOnlyChange
}: SearchBarProps) => {
return (
<View className="p-4 bg-white border-b border-gray-200">
<TextInput
value={filterText}
onChangeText={onFilterTextChange}
placeholder="Search..."
className="border border-gray-300 rounded-lg px-4 py-2 mb-3"
/>
<View className="flex-row items-center">
<Pressable
onPress={() => onInStockOnlyChange(!inStockOnly)}
className="mr-2"
>
{/* Checkbox UI */}
</Pressable>
<Text>Only show products in stock</Text>
</View>
</View>
);
};
const ProductList = ({ products, filterText, inStockOnly }: ProductListProps) => {
const filteredProducts = products.filter((product) => {
const matchesSearch = product.name
.toLowerCase()
.includes(filterText.toLowerCase());
const matchesStock = !inStockOnly || product.stocked;
return matchesSearch && matchesStock;
});
// Group by category
const groupedProducts: { [key: string]: Product[] } = {};
filteredProducts.forEach((product) => {
if (!groupedProducts[product.category]) {
groupedProducts[product.category] = [];
}
groupedProducts[product.category].push(product);
});
const renderItem = ({ item }: { item: { category: string; products: Product[] } }) => (
<View>
<ProductCategoryHeader category={item.category} />
{item.products.map((product) => (
<ProductItem key={product.name} product={product} />
))}
</View>
);
const data = Object.entries(groupedProducts).map(([category, products]) => ({
category,
products,
}));
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.category}
className="flex-1"
/>
);
};
FlatList for long lists (better performance than ScrollView)keyExtractor for unique keysgetItemLayout if item heights are fixed (performance optimization)Pressable - Modern, flexible touchable componentTouchableOpacity - Simple button with opacity feedbackTouchableHighlight - Button with highlight feedbackclassName prop instead of styleclassName={isActive ? "bg-blue-500" : "bg-gray-300"}any)When building a new feature:
classNameconst [value, setValue] = useState('');
<TextInput
value={value}
onChangeText={setValue}
placeholder="Enter text..."
/>
const [isChecked, setIsChecked] = useState(false);
<Pressable onPress={() => setIsChecked(!isChecked)}>
<View className={`w-5 h-5 border-2 rounded ${isChecked ? 'bg-blue-500' : 'bg-white'}`}>
{isChecked && <Text>✓</Text>}
</View>
</Pressable>
const filteredItems = items.filter((item) => {
return item.name.toLowerCase().includes(searchText.toLowerCase());
});