DailyDevDiet

logo - dailydevdiet

Learn. Build. Innovate. Elevate your coding skills with dailydevdiet!

Chapter 6: State and Props in React Native

State and Props in React Native

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

AspectStateProps
MutabilityMutable (can be changed)Immutable (read-only)
OwnershipOwned by the componentPassed from parent
UsageInternal component dataCommunication between components
UpdatesCan be updated with setState/useStateCannot be modified directly
Re-renderingTriggers re-render when changedTriggers 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

  1. Keep state minimal: Only put data in state that changes and affects the render
  2. Don’t duplicate data: Derive values from existing state when possible
  3. Use functional updates: When the new state depends on the previous state
  4. 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

  1. Use PropTypes or TypeScript: For better type checking and documentation
  2. Provide default props: For optional props that might not be passed
  3. Keep props interface minimal: Don’t pass unnecessary props
  4. 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

  1. Props flow downward: Data flows from parent to child components through props
  2. State is local: Each component manages its own state independently
  3. Functions as props: You can pass functions as props to enable child-to-parent communication
  4. Immutability: Always create new objects/arrays when updating state
  5. Performance: Minimize unnecessary re-renders by keeping state structure simple
  6. 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

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:

Scroll to Top