Handling User Input with TextInput
User Input with TextInput
~9 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Every app eventually needs text input – a search box, a login form, an address. In React Native that's handled by the TextInput component.
Basics: controlled input fields
Unlike an HTML <input>, whose value simply lives in the DOM, a TextInput in React Native (and React in general) is usually a "controlled" field: its value comes from state, and every keystroke updates that state.
import { useState } from 'react';
import { TextInput, View, StyleSheet } from 'react-native';
function SearchField() {
const [searchText, setSearchText] = useState('');
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Search products..."
value={searchText}
onChangeText={setSearchText}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
input: {
borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 8,
padding: 10,
fontSize: 16,
},
});| HTML/Web | React Native |
|---|---|
<input type="text" placeholder="..."> | <TextInput placeholder="..." /> |
input.addEventListener('input', e => ...) | onChangeText={{(text) => ...}} – receives the new text value directly, no event object needed |
input.value = searchText (set imperatively) | value={{searchText}} (declarative, from state) |
<input type="password"> | <TextInput secureTextEntry /> |
<input type="email" inputmode="email"> | <TextInput keyboardType="email-address" /> – shows the matching on-screen keyboard |
CSS :focus styling | onFocus / onBlur callback props + your own state |
Extending ProductListScreen.js with live search
Now let's build this directly into our real screens/ProductListScreen.js from chapter 10. Replace the entire content of the file with this extended version:
import { useState, useEffect } from 'react';
import {
View,
Text,
FlatList,
TextInput,
StyleSheet,
ActivityIndicator,
} from 'react-native';
import { fetchProducts } from '../api/magentoApi';
import ProductCard from '../components/ProductCard';
function ProductListScreen({ navigation }) {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [searchText, setSearchText] = useState('');
useEffect(() => {
async function loadProducts() {
try {
const items = await fetchProducts();
setProducts(items);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
loadProducts();
}, []);
if (loading) {
return <ActivityIndicator size="large" style={styles.loader} />;
}
const filteredProducts = products.filter((product) =>
product.name.toLowerCase().includes(searchText.toLowerCase())
);
return (
<View style={styles.container}>
<TextInput
style={styles.searchInput}
placeholder="Search products..."
value={searchText}
onChangeText={setSearchText}
/>
<FlatList
data={filteredProducts}
keyExtractor={(item) => item.sku}
renderItem={({ item }) => (
<ProductCard
name={item.name}
price={item.price}
imageUrl={item.imageUrl}
onPress={() =>
navigation.navigate('ProductDetail', { productSku: item.sku })
}
/>
)}
ListEmptyComponent={<Text style={styles.emptyText}>No products found.</Text>}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 16, paddingHorizontal: 16 },
loader: { flex: 1, justifyContent: 'center' },
searchInput: {
borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 8,
padding: 10,
fontSize: 16,
marginBottom: 12,
},
emptyText: { textAlign: 'center', marginTop: 24, color: '#6b7280' },
});
export default ProductListScreen;What's new is the searchText state, the TextInput field above the list, and the line const filteredProducts = products.filter(...). On every keystroke, searchText changes via setSearchText, the component re-renders, filteredProducts gets recalculated, and the FlatList automatically shows only matching results – no manual DOM filtering needed, unlike setting element.style.display = 'none' per row on the web. Note that the FlatList now receives filteredProducts instead of products as its data.
Achtung: For large product lists (hundreds/thousands of entries), filtering should ideally happen server-side through the search API (with Magento, e.g. the /rest/V1/search endpoint or Elasticsearch), rather than iterating the full array client-side – you'll learn this in an advanced tutorial section once this series is extended with a debounce technique.