whenever life put's you in a tough situtation, never say why me! but, try me!

Chapter 14: Expo React Native State Management with Hooks - useState and useEffect


In this chapter, we'll dive deeper into React Native state management using the useState and useEffect hooks. These hooks are fundamental for handling local state and side effects in functional components. We'll explore their usage individually and how they can be combined to create powerful, interactive components.


1. Managing Local State with useState

The useState hook allows you to add state to functional components. It is used to declare state variables and provides a mechanism to update them.

Key Points:

  • Syntax: const [state, setState] = useState(initialState);
  • State Update Function: The function returned by useState is used to update the state, which triggers a re-render of the component.
  • Multiple State Variables: You can have multiple useState calls to manage different pieces of state separately.

Example: Basic Counter with useState

import React, { useState } from "react";
import { View, Text, Button, StyleSheet } from "react-native";

export default function Counter() {
  // Initialize a state variable `count` with initial value 0
  const [count, setCount] = useState(0);

  return (
    <View style={styles.container}>
      <Text style={styles.text}>Count: {count}</Text>
      <Button title="Increase" onPress={() => setCount(count + 1)} />
      <Button title="Decrease" onPress={() => setCount(count - 1)} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontSize: 24,
    marginBottom: 20,
  },
});

Explanation:

  • count is the state variable, and setCount is the function to update count.
  • Each button press calls setCount, which updates the state and re-renders the component with the new state value.

2. Side Effects with useEffect

The useEffect hook allows you to perform side effects in your components, such as data fetching, subscriptions, or manually changing the DOM.

Key Points:

  • Syntax: useEffect(() => { /* side effect */ }, [dependencies]);
  • Dependencies Array: Determines when the effect should re-run. If omitted, the effect runs after every render. If an empty array is passed, the effect only runs once (on mount and unmount).
  • Cleanup Function: Optional return function for cleanup (like removing event listeners or canceling API requests) when the component is unmounted or before the effect re-runs.

Example: Fetch Data with useEffect

import React, { useState, useEffect } from "react";
import { View, Text, ActivityIndicator, StyleSheet } from "react-native";

export default function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetch data when component mounts
    fetch("https://jsonplaceholder.typicode.com/posts/1")
      .then((response) => response.json())
      .then((json) => {
        setData(json);
        setLoading(false);
      })
      .catch((error) => console.error(error));
  }, []); // Empty dependency array ensures this effect runs only once

  return (
    <View style={styles.container}>
      {loading ? (
        <ActivityIndicator size="large" color="#0000ff" />
      ) : (
        <Text style={styles.text}>{data.title}</Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontSize: 18,
    textAlign: "center",
    margin: 10,
  },
});

Explanation:

  • useEffect fetches data when the component mounts and sets the loading state to false once data is fetched.
  • The empty dependency array [] ensures that the fetch operation is only performed once, similar to the componentDidMount lifecycle method in class components.

3. Combining Both useEffect and useState

Combining useState and useEffect is a common pattern for managing state and side effects together. This allows for more dynamic and responsive components.

Example: Search Filter with useState and useEffect

import React, { useState, useEffect } from "react";
import { View, Text, TextInput, FlatList, StyleSheet } from "react-native";

export default function SearchFilter() {
  const [query, setQuery] = useState("");
  const [filteredData, setFilteredData] = useState([]);
  const [data, setData] = useState([
    { id: "1", name: "Apple" },
    { id: "2", name: "Banana" },
    { id: "3", name: "Cherry" },
    { id: "4", name: "Date" },
    { id: "5", name: "Elderberry" },
  ]);

  useEffect(() => {
    // Filter data whenever query changes
    setFilteredData(
      data.filter((item) =>
        item.name.toLowerCase().includes(query.toLowerCase())
      )
    );
  }, [query]); // Re-run this effect whenever `query` changes

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="Search..."
        value={query}
        onChangeText={setQuery}
      />
      <FlatList
        data={filteredData}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  input: {
    height: 40,
    borderColor: "gray",
    borderWidth: 1,
    marginBottom: 20,
    paddingHorizontal: 10,
  },
  item: {
    padding: 10,
    fontSize: 18,
  },
});

Explanation:

  • useState manages query, filteredData, and data.
  • useEffect updates filteredData whenever query changes.
  • This combination allows for a reactive search feature that filters the list based on user input.

4. Advanced Usage of useEffect and useState

To master state management with useEffect and useState, you need to understand their advanced uses, such as handling component unmounting and multiple side effects.

Advanced Example: Polling Data with Cleanup

import React, { useState, useEffect } from "react";
import { View, Text, Button, StyleSheet } from "react-native";

export default function PollingExample() {
  const [data, setData] = useState(null);
  const [isPolling, setIsPolling] = useState(false);

  useEffect(() => {
    let interval;
    if (isPolling) {
      interval = setInterval(() => {
        fetch("https://jsonplaceholder.typicode.com/posts/1")
          .then((response) => response.json())
          .then((json) => setData(json))
          .catch((error) => console.error(error));
      }, 5000); // Poll every 5 seconds
    }

    return () => {
      if (interval) clearInterval(interval); // Cleanup on unmount or when isPolling changes
    };
  }, [isPolling]); // Re-run effect whenever `isPolling` changes

  return (
    <View style={styles.container}>
      <Text style={styles.text}>{data ? data.title : "No data"}</Text>
      <Button
        title={isPolling ? "Stop Polling" : "Start Polling"}
        onPress={() => setIsPolling(!isPolling)}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontSize: 18,
    textAlign: "center",
    margin: 10,
  },
});

Explanation:

  • useState manages data and isPolling.
  • useEffect handles the side effect of polling data from an API every 5 seconds when isPolling is true.
  • Cleanup Function: Clears the interval when the component unmounts or isPolling changes.

By combining useState and useEffect, you can manage both local state and side effects effectively in your Expo React Native application. This chapter provides the foundation to handle most state management scenarios using these hooks, enabling you to create responsive and dynamic mobile applications.