
Introduction
State and Props in React Native are two fundamental concepts that control how data flows through your application and how components communicate with each other. Understanding these concepts is crucial for building dynamic and interactive mobile applications.
Understanding Props
Props (short for properties) are a way to pass data from parent components to child components. They are read-only and help make components reusable and configurable.
Basic Props Usage
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
// Child component that receives props
const Greeting = (props) => {
return (
<View style={styles.container}>
<Text style={styles.text}>Hello, {props.name}!</Text>
<Text style={styles.subtext}>You are {props.age} years old.</Text>
</View>
);
};
// Parent component that passes props
const App = () => {
return (
<View style={styles.app}>
<Greeting name="Alice" age={25} />
<Greeting name="Bob" age={30} />
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
container: {
padding: 20,
margin: 10,
backgroundColor: 'white',
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
text: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
subtext: {
fontSize: 14,
color: '#666',
marginTop: 5,
},
});
export default App;
Destructuring Props
// Instead of using props.name and props.age
const Greeting = (props) => {
return (
<View>
<Text>Hello, {props.name}!</Text>
</View>
);
};
// You can destructure props for cleaner code
const Greeting = ({ name, age, isStudent = false }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>Hello, {name}!</Text>
<Text style={styles.subtext}>
You are {age} years old
{isStudent && ' and you are a student'}
</Text>
</View>
);
};
PropTypes for Type Checking
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text } from 'react-native';
const UserProfile = ({ name, age, email, isActive }) => {
return (
<View style={styles.profile}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.info}>Age: {age}</Text>
<Text style={styles.info}>Email: {email}</Text>
<Text style={[styles.status, { color: isActive ? 'green' : 'red' }]}>
Status: {isActive ? 'Active' : 'Inactive'}
</Text>
</View>
);
};
UserProfile.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string.isRequired,
isActive: PropTypes.bool,
};
UserProfile.defaultProps = {
isActive: false,
};
Complex Props (Objects and Functions)
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
const UserCard = ({ user, onPress, style }) => {
return (
<TouchableOpacity style={[styles.card, style]} onPress={() => onPress(user)}>
<Text style={styles.name}>{user.firstName} {user.lastName}</Text>
<Text style={styles.email}>{user.email}</Text>
<Text style={styles.phone}>{user.phone}</Text>
</TouchableOpacity>
);
};
const App = () => {
const users = [
{
id: 1,
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
phone: '+1-234-567-8900',
},
{
id: 2,
firstName: 'Jane',
lastName: 'Smith',
email: 'jane@example.com',
phone: '+1-234-567-8901',
},
];
const handleUserPress = (user) => {
console.log('User pressed:', user.firstName);
// Navigate to user details or perform action
};
return (
<View style={styles.container}>
{users.map((user) => (
<UserCard
key={user.id}
user={user}
onPress={handleUserPress}
style={{ marginBottom: 10 }}
/>
))}
</View>
);
};
Understanding State
State is a JavaScript object that stores property values that belong to a component. When the state changes, the component re-renders to reflect the new state.
Class Component State
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
message: 'Initial message',
};
}
increaseCount = () => {
this.setState({
count: this.state.count + 1,
message: `Count increased to ${this.state.count + 1}`,
});
};
decreaseCount = () => {
this.setState((prevState) => ({
count: prevState.count - 1,
message: `Count decreased to ${prevState.count - 1}`,
}));
};
resetCount = () => {
this.setState({
count: 0,
message: 'Count reset to zero',
});
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Counter App</Text>
<Text style={styles.count}>{this.state.count}</Text>
<Text style={styles.message}>{this.state.message}</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.increaseButton]}
onPress={this.increaseCount}
>
<Text style={styles.buttonText}>+</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.resetButton]}
onPress={this.resetCount}
>
<Text style={styles.buttonText}>Reset</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.decreaseButton]}
onPress={this.decreaseCount}
>
<Text style={styles.buttonText}>-</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Functional Component State with useState Hook
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
const CounterFunctional = () => {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('Initial message');
const increaseCount = () => {
const newCount = count + 1;
setCount(newCount);
setMessage(`Count increased to ${newCount}`);
};
const decreaseCount = () => {
const newCount = count - 1;
setCount(newCount);
setMessage(`Count decreased to ${newCount}`);
};
const resetCount = () => {
setCount(0);
setMessage('Count reset to zero');
};
return (
<View style={styles.container}>
<Text style={styles.title}>Functional Counter</Text>
<Text style={styles.count}>{count}</Text>
<Text style={styles.message}>{message}</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.increaseButton]}
onPress={increaseCount}
>
<Text style={styles.buttonText}>+</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.resetButton]}
onPress={resetCount}
>
<Text style={styles.buttonText}>Reset</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.decreaseButton]}
onPress={decreaseCount}
>
<Text style={styles.buttonText}>-</Text>
</TouchableOpacity>
</View>
</View>
);
};
Complex State Management
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, TextInput, StyleSheet } from 'react-native';
const TodoApp = () => {
const [todos, setTodos] = useState([]);
const [inputText, setInputText] = useState('');
const [filter, setFilter] = useState('all'); // all, completed, pending
const addTodo = () => {
if (inputText.trim()) {
const newTodo = {
id: Date.now(),
text: inputText.trim(),
completed: false,
createdAt: new Date(),
};
setTodos([...todos, newTodo]);
setInputText('');
}
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const getFilteredTodos = () => {
switch (filter) {
case 'completed':
return todos.filter(todo => todo.completed);
case 'pending':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
};
const TodoItem = ({ todo }) => (
<View style={styles.todoItem}>
<TouchableOpacity onPress={() => toggleTodo(todo.id)} style={styles.todoText}>
<Text style={[
styles.todoTextContent,
todo.completed && styles.completed
]}>
{todo.text}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => deleteTodo(todo.id)} style={styles.deleteButton}>
<Text style={styles.deleteButtonText}>Delete</Text>
</TouchableOpacity>
</View>
);
return (
<View style={styles.container}>
<Text style={styles.title}>Todo App</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
value={inputText}
onChangeText={setInputText}
placeholder="Enter todo..."
onSubmitEditing={addTodo}
/>
<TouchableOpacity style={styles.addButton} onPress={addTodo}>
<Text style={styles.addButtonText}>Add</Text>
</TouchableOpacity>
</View>
<View style={styles.filterContainer}>
{['all', 'pending', 'completed'].map(filterType => (
<TouchableOpacity
key={filterType}
style={[
styles.filterButton,
filter === filterType && styles.activeFilter
]}
onPress={() => setFilter(filterType)}
>
<Text style={[
styles.filterButtonText,
filter === filterType && styles.activeFilterText
]}>
{filterType.charAt(0).toUpperCase() + filterType.slice(1)}
</Text>
</TouchableOpacity>
))}
</View>
<View style={styles.statsContainer}>
<Text style={styles.stats}>
Total: {todos.length} |
Completed: {todos.filter(t => t.completed).length} |
Pending: {todos.filter(t => !t.completed).length}
</Text>
</View>
<View style={styles.todoList}>
{getFilteredTodos().map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</View>
</View>
);
};
State vs Props: Key Differences
Aspect | State | Props |
Mutability | Mutable (can be changed) | Immutable (read-only) |
Ownership | Owned by the component | Passed from parent |
Usage | Internal component data | Communication between components |
Updates | Can be updated with setState/useState | Cannot be modified directly |
Re-rendering | Triggers re-render when changed | Triggers re-render when props change |
Passing State as Props
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
// Child component that receives state as props
const DisplayCount = ({ count, onIncrement, onDecrement }) => {
return (
<View style={styles.displayContainer}>
<Text style={styles.countText}>Current Count: {count}</Text>
<View style={styles.buttonRow}>
<TouchableOpacity style={styles.actionButton} onPress={onIncrement}>
<Text style={styles.buttonText}>+</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={onDecrement}>
<Text style={styles.buttonText}>-</Text>
</TouchableOpacity>
</View>
</View>
);
};
// Another child component
const CountHistory = ({ history }) => {
return (
<View style={styles.historyContainer}>
<Text style={styles.historyTitle}>Count History:</Text>
{history.map((item, index) => (
<Text key={index} style={styles.historyItem}>
{index + 1}. {item.action} → {item.value} (at {item.timestamp})
</Text>
))}
</View>
);
};
// Parent component that manages state
const CounterWithHistory = () => {
const [count, setCount] = useState(0);
const [history, setHistory] = useState([]);
const addToHistory = (action, value) => {
const timestamp = new Date().toLocaleTimeString();
setHistory(prev => [...prev, { action, value, timestamp }]);
};
const increment = () => {
const newCount = count + 1;
setCount(newCount);
addToHistory('Increment', newCount);
};
const decrement = () => {
const newCount = count - 1;
setCount(newCount);
addToHistory('Decrement', newCount);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter with History</Text>
{/* Passing state as props to child components */}
<DisplayCount
count={count}
onIncrement={increment}
onDecrement={decrement}
/>
<CountHistory history={history} />
</View>
);
};
State Lifting
Sometimes you need to share state between sibling components. In such cases, you lift the state up to their common parent.
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
// Component A that needs to display user info
const UserDisplay = ({ user }) => {
if (!user) {
return <Text style={styles.noUser}>No user selected</Text>;
}
return (
<View style={styles.userDisplay}>
<Text style={styles.userName}>{user.name}</Text>
<Text style={styles.userEmail}>{user.email}</Text>
<Text style={styles.userRole}>{user.role}</Text>
</View>
);
};
// Component B that allows user selection
const UserSelector = ({ users, onUserSelect, selectedUser }) => {
return (
<View style={styles.userSelector}>
<Text style={styles.selectorTitle}>Select a User:</Text>
{users.map(user => (
<TouchableOpacity
key={user.id}
style={[
styles.userButton,
selectedUser?.id === user.id && styles.selectedUserButton
]}
onPress={() => onUserSelect(user)}
>
<Text style={[
styles.userButtonText,
selectedUser?.id === user.id && styles.selectedUserButtonText
]}>
{user.name}
</Text>
</TouchableOpacity>
))}
</View>
);
};
// Parent component that lifts state up
const UserManager = () => {
const [selectedUser, setSelectedUser] = useState(null);
const users = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Manager' },
{ id: 2, name: 'Bob Wilson', email: 'bob@example.com', role: 'Developer' },
{ id: 3, name: 'Carol Brown', email: 'carol@example.com', role: 'Designer' },
];
return (
<View style={styles.container}>
<Text style={styles.title}>User Management System</Text>
{/* Both components share the selectedUser state */}
<UserSelector
users={users}
onUserSelect={setSelectedUser}
selectedUser={selectedUser}
/>
<UserDisplay user={selectedUser} />
</View>
);
};
Best Practices
State Best Practices
- Keep state minimal: Only put data in state that changes and affects the render
- Don’t duplicate data: Derive values from existing state when possible
- Use functional updates: When the new state depends on the previous state
- Avoid nested state: Keep state structure flat for easier updates
// ✅ Good: Functional update
const increment = () => {
setCount(prevCount => prevCount + 1);
};
// ❌ Avoid: Direct state dependency
const increment = () => {
setCount(count + 1); // This might be stale
};
// ✅ Good: Derived state
const TodoList = () => {
const [todos, setTodos] = useState([]);
// Derive these values instead of storing them in state
const completedCount = todos.filter(todo => todo.completed).length;
const pendingCount = todos.length - completedCount;
// ... rest of component
};
Props Best Practices
- Use PropTypes or TypeScript: For better type checking and documentation
- Provide default props: For optional props that might not be passed
- Keep props interface minimal: Don’t pass unnecessary props
- Use composition: Instead of passing many props
// ✅ Good: Composition pattern
const Card = ({ children, style }) => (
<View style={[styles.card, style]}>
{children}
</View>
);
const UserCard = ({ user }) => (
<Card>
<Text>{user.name}</Text>
<Text>{user.email}</Text>
</Card>
);
// ❌ Avoid: Too many props
const Card = ({ title, subtitle, content, actions, style, onPress, disabled }) => {
// Component becomes complex with too many props
};
Common Patterns and Anti-Patterns
Common Patterns
1. Controlled Components
const ControlledInput = () => {
const [value, setValue] = useState('');
return (
<TextInput
value={value}
onChangeText={setValue}
placeholder="Controlled input"
/>
);
};
2. Form State Management
const ContactForm = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const updateField = (field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = () => {
console.log('Form submitted:', formData);
// Validate and submit form
};
return (
<View>
<TextInput
value={formData.name}
onChangeText={(value) => updateField('name', value)}
placeholder="Name"
/>
<TextInput
value={formData.email}
onChangeText={(value) => updateField('email', value)}
placeholder="Email"
/>
<TextInput
value={formData.message}
onChangeText={(value) => updateField('message', value)}
placeholder="Message"
multiline
/>
<TouchableOpacity onPress={handleSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
</View>
);
};
Common Anti-Patterns
1. Mutating State Directly
// ❌ Don't do this
const addItem = (newItem) => {
items.push(newItem); // Mutating state directly
setItems(items);
};
// ✅ Do this instead
const addItem = (newItem) => {
setItems([...items, newItem]); // Create new array
};
2. Storing Props in State
// ❌ Don't do this
const UserProfile = ({ user }) => {
const [userData, setUserData] = useState(user);
// userData won't update when props change
};
// ✅ Do this instead
const UserProfile = ({ user }) => {
// Use props directly or derive state from props
const displayName = user.firstName + ' ' + user.lastName;
};
Practical Exercise
Create a shopping cart application that demonstrates both state and props usage:
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
const ProductItem = ({ product, onAddToCart, inCart }) => (
<View style={styles.productItem}>
<Text style={styles.productName}>{product.name}</Text>
<Text style={styles.productPrice}>${product.price}</Text>
<TouchableOpacity
style={[styles.addButton, inCart && styles.inCartButton]}
onPress={() => onAddToCart(product)}
disabled={inCart}
>
<Text style={styles.addButtonText}>
{inCart ? 'In Cart' : 'Add to Cart'}
</Text>
</TouchableOpacity>
</View>
);
const CartItem = ({ item, onUpdateQuantity, onRemove }) => (
<View style={styles.cartItem}>
<Text style={styles.cartItemName}>{item.name}</Text>
<View style={styles.quantityContainer}>
<TouchableOpacity onPress={() => onUpdateQuantity(item.id, item.quantity - 1)}>
<Text style={styles.quantityButton}>-</Text>
</TouchableOpacity>
<Text style={styles.quantity}>{item.quantity}</Text>
<TouchableOpacity onPress={() => onUpdateQuantity(item.id, item.quantity + 1)}>
<Text style={styles.quantityButton}>+</Text>
</TouchableOpacity>
</View>
<Text style={styles.cartItemPrice}>${(item.price * item.quantity).toFixed(2)}</Text>
<TouchableOpacity onPress={() => onRemove(item.id)}>
<Text style={styles.removeButton}>Remove</Text>
</TouchableOpacity>
</View>
);
const ShoppingCart = () => {
const [cart, setCart] = useState([]);
const products = [
{ id: 1, name: 'iPhone 13', price: 999.99 },
{ id: 2, name: 'Samsung Galaxy S21', price: 799.99 },
{ id: 3, name: 'iPad Pro', price: 1099.99 },
{ id: 4, name: 'MacBook Air', price: 1299.99 },
];
const addToCart = (product) => {
const existingItem = cart.find(item => item.id === product.id);
if (existingItem) {
setCart(cart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
));
} else {
setCart([...cart, { ...product, quantity: 1 }]);
}
};
const updateQuantity = (id, newQuantity) => {
if (newQuantity <= 0) {
removeFromCart(id);
} else {
setCart(cart.map(item =>
item.id === id ? { ...item, quantity: newQuantity } : item
));
}
};
const removeFromCart = (id) => {
setCart(cart.filter(item => item.id !== id));
};
const getTotalPrice = () => {
return cart.reduce((total, item) => total + (item.price * item.quantity), 0);
};
const isProductInCart = (productId) => {
return cart.some(item => item.id === productId);
};
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>Shopping Cart Demo</Text>
<Text style={styles.sectionTitle}>Products</Text>
{products.map(product => (
<ProductItem
key={product.id}
product={product}
onAddToCart={addToCart}
inCart={isProductInCart(product.id)}
/>
))}
<Text style={styles.sectionTitle}>
Cart ({cart.length} items)
</Text>
{cart.length === 0 ? (
<Text style={styles.emptyCart}>Your cart is empty</Text>
) : (
<>
{cart.map(item => (
<CartItem
key={item.id}
item={item}
onUpdateQuantity={updateQuantity}
onRemove={removeFromCart}
/>
))}
<View style={styles.totalContainer}>
<Text style={styles.totalText}>
Total: ${getTotalPrice().toFixed(2)}
</Text>
</View>
</>
)}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
color: '#333',
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
color: '#333',
},
productItem: {
backgroundColor: 'white',
padding: 16,
marginBottom: 10,
borderRadius: 8,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
},
productName: {
fontSize: 16,
fontWeight: 'bold',
flex: 1,
},
productPrice: {
fontSize: 16,
color: '#666',
marginRight: 10,
},
addButton: {
backgroundColor: '#007AFF',
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 4,
},
inCartButton: {
backgroundColor: '#4CAF50',
},
addButtonText: {
color: 'white',
fontWeight: 'bold',
},
cartItem: {
backgroundColor: 'white',
padding: 16,
marginBottom: 10,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
},
cartItemName: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
quantityContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
quantityButton: {
fontSize: 20,
paddingHorizontal: 12,
paddingVertical: 4,
backgroundColor: '#f0f0f0',
borderRadius: 4,
textAlign: 'center',
minWidth: 30,
},
quantity: {
fontSize: 16,
marginHorizontal: 16,
fontWeight: 'bold',
},
cartItemPrice: {
fontSize: 16,
color: '#666',
marginBottom: 8,
},
removeButton: {
color: '#FF3B30',
fontWeight: 'bold',
},
emptyCart: {
textAlign: 'center',
fontSize: 16,
color: '#666',
marginTop: 20,
},
totalContainer: {
backgroundColor: 'white',
padding: 16,
marginTop: 10,
borderRadius: 8,
alignItems: 'center',
},
totalText: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
},
});
export default ShoppingCart;
Summary
In this chapter, we covered the fundamental concepts of state and props in React Native:
- Props are used to pass data from parent to child components and are read-only
- State is used to manage data that changes within a component and triggers re-renders
- useState Hook provides a modern way to manage state in functional components
- State lifting allows sharing state between sibling components
- PropTypes help with type checking and documentation
- Best practices include keeping state minimal, using functional updates, and avoiding direct mutations
Understanding state and props is crucial for building interactive React Native applications. These concepts form the foundation for more advanced patterns like context, reducers, and state management libraries.
Key Takeaways
- Props flow downward: Data flows from parent to child components through props
- State is local: Each component manages its own state independently
- Functions as props: You can pass functions as props to enable child-to-parent communication
- Immutability: Always create new objects/arrays when updating state
- Performance: Minimize unnecessary re-renders by keeping state structure simple
- Composition over complex props: Use component composition instead of passing many props
Practice Exercises
Exercise 1: Profile Card Component
Create a reusable ProfileCard component that accepts user data as props and displays:
- Profile picture (use placeholder)
- Name and email
- Bio text
- Social media links
- Follow/Unfollow button with state
Exercise 2: Simple Calculator
Build a calculator app that demonstrates:
- State management for current value and operation
- Props for button components
- History of calculations stored in state
- Clear and reset functionality
Exercise 3: Contact List
Create a contact management app with:
- List of contacts passed as props to ContactList component
- Search functionality using state
- Add/Edit/Delete operations
- Favorite contacts toggle
Additional Resources
- React Native State and Lifecycle
- React Hooks Documentation
- PropTypes Documentation
- React Component Patterns
What’s Next?
In the next chapter, we’ll explore React Native Navigation, learning how to create multi-screen applications and pass data between different screens. We’ll also cover advanced navigation patterns and best practices for structuring navigation in React Native apps.
Related Articles:
- React Native vs Flutter in 2024: A Detailed Comparison
- Top 10 React Native Libraries You Must Know in 2024
- React vs React Native: A Deep Dive into Key Differences
- How to Set Up Navigation in React Native : Step-by-Step Guide
- What is Axios? Fetch vs Axios: What’s the Difference?
- React Native Environment Setup: A Beginner’s Step-by-Step Guide
- React Native Web: A Step-by-Step Guide to Cross-Platform Development