Paramclasses Academy - Blog

Tutorials Details

←
Post 25 of 39
→

What is React Hooks in React Native: useState, useEffect and useContext

What is React Hooks in React Native: useState, useEffect and useContext

November 5, 2026

1. Introduction

React Hooks are special functions that allow functional components to use React features such as state, side effects, and shared application data.

Before Hooks, developers commonly used class components to manage state and lifecycle methods. Hooks make it possible to organize these features inside functional components.

In this tutorial, we will learn three important Hooks:

  1. useState – Manage component state.

  2. useEffect – Perform side effects such as API requests.

  3. useContext – Share data between components without passing props through every level.

By the end, we will build a practical Student Management App using all three Hooks.

2. What Are React Hooks?

Hooks are JavaScript functions provided by React. Their names generally begin with the word use.

For example:

import { useState, useEffect, useContext } from 'react';

Hooks allow functional components to manage information, respond to changes, and share data across the component tree.

Why Do We Use Hooks?

  • To manage application state.

  • To fetch data from APIs.

  • To perform actions when a component appears or updates.

  • To share data between multiple components.

  • To reuse stateful logic in custom Hooks.

  • To keep components easier to understand and maintain.

3. Understanding useState

useState is used to create and update state inside a functional component.

State is information that a component remembers. When state changes, React renders the component again so that the interface can display the updated value.

Syntax

const [state, setState] = useState(initialValue);

Explanation:

  • state: Stores the current value.

  • setState: Updates the state.

  • initialValue: The starting value.

Example: Counter App

Create a new React Native component:

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

export default function CounterApp() {
  const [count, setCount] = useState(0);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Counter App</Text>

      <Text style={styles.count}>
        Count: {count}
      </Text>

      <Button
        title="Increase"
        onPress={() => setCount(count + 1)}
      />

      <Button
        title="Decrease"
        onPress={() => setCount(count - 1)}
      />

      <Button
        title="Reset"
        onPress={() => setCount(0)}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 26,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  count: {
    fontSize: 30,
    marginBottom: 20,
  },
});

How Does This Code Work?

  1. useState(0) creates a state variable with an initial value of zero.

  2. count stores the current number.

  3. setCount() updates the number.

  4. onPress calls the state update function when a button is pressed.

  5. React renders the component again with the updated count.

Important: When the next value depends on the previous state, use the functional update form:

setCount(previousCount => previousCount + 1);

This helps ensure that the update uses the latest queued state value.

4. Understanding useEffect

useEffect is used to synchronize a component with something outside React, such as:

  • Fetching data from an API.

  • Starting or stopping a timer.

  • Setting up subscriptions.

  • Listening to external events.

  • Synchronizing with external services.

Syntax

useEffect(() => {
  // Effect logic

  return () => {
    // Cleanup logic
  };
}, [dependencies]);

The cleanup function is optional. It is useful when an effect creates something that must be stopped or cleaned up.

Understanding the Dependency Array

A. Run after every render

useEffect(() => {
  console.log('Component rendered');
});

This runs after every committed render.

B. Run when the component first appears

useEffect(() => {
  console.log('Component mounted');
}, []);

An empty dependency array means the effect has no reactive dependencies. It runs after the initial commit, with an additional development-only setup and cleanup cycle possible in Strict Mode.

C. Run when a specific value changes

useEffect(() => {
  console.log('Count changed:', count);
}, [count]);

This effect runs after the initial commit and again when count changes.

Example: Timer Using useEffect

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

export default function TimerApp() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setSeconds(previous => previous + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Timer App</Text>

      <Text style={styles.timer}>
        {seconds} Seconds
      </Text>

      <Button
        title="Reset Timer"
        onPress={() => setSeconds(0)}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 26,
    fontWeight: 'bold',
  },
  timer: {
    fontSize: 32,
    marginVertical: 20,
  },
});

How Does This Code Work?

  1. The timer starts after the component's initial commit.

  2. setInterval() runs every second.

  3. setSeconds() increases the seconds value.

  4. React updates the displayed timer.

  5. When the component is removed, the cleanup function clears the interval.

Why Is Cleanup Important?

Without clearInterval(), an interval could continue running after its component is removed. Cleanup helps prevent unnecessary background work and related bugs.

5. Understanding useContext

In a large application, many components may need access to the same information.

For example, a Student Management App may have:

  • A student list screen.

  • An add-student component.

  • A student summary component.

  • A dashboard component.

Passing the same data through every intermediate component can make the code difficult to maintain. This is known as prop drilling.

useContext allows components to read shared data from a Context Provider without passing props through every level.

Step 1: Create a Context

import { createContext } from 'react';

export const StudentContext = createContext(null);

Step 2: Create a Provider

A Provider makes shared data available to its child components.

import React, { useState } from 'react';
import { StudentContext } from './StudentContext';

export default function StudentProvider({ children }) {
  const [students, setStudents] = useState([]);

  const addStudent = (student) => {
    setStudents(previous => [...previous, student]);
  };

  const deleteStudent = (id) => {
    setStudents(previous =>
      previous.filter(student => student.id !== id)
    );
  };

  return (
    <StudentContext.Provider
      value={{
        students,
        addStudent,
        deleteStudent,
      }}
    >
      {children}
    </StudentContext.Provider>
  );
}

Step 3: Access Context Using useContext

import React, { useContext } from 'react';
import { StudentContext } from './StudentContext';

function StudentList() {
  const {
    students,
    deleteStudent,
  } = useContext(StudentContext);

  return (
    // Use students and deleteStudent here
    null
  );
}

The component receives the value from the nearest matching Provider above it in the component tree.

6. Difference Between useState, useEffect and useContext

Hook Main Purpose Example
useState Manage local state Counter value
useEffect Synchronize with external systems API request
useContext Read shared application data Student list

Remember:

  • Use useState when a component needs to remember and update a value.

  • Use useEffect when a component needs to synchronize with an external system.

  • Use useContext when multiple components need access to shared data.

7. Practical Project: Student Management App

Now we will combine all three Hooks in one practical application.

Project Features

  • Fetch student data from a REST API.

  • Display student records.

  • Add a new student.

  • Delete a student.

  • Share student data through Context.

  • Show loading and error messages.

For this demonstration, we will use JSONPlaceholder as a sample API. It provides sample user data; changes made in this app will remain local to the running application and will not be permanently saved on that service.

Step 1: Create the Expo Project

Open your terminal:

npx create-expo-app@latest StudentHooksApp --template blank

Open the project:

cd StudentHooksApp

Start the application:

npx expo start

Step 2: Create the Project Structure

Create these files:

StudentHooksApp/
│
├── App.js
│
└── src/
    ├── StudentContext.js
    ├── StudentProvider.js
    └── StudentScreen.js

Step 3: Create StudentContext.js

import { createContext } from 'react';

export const StudentContext = createContext(null);

This file creates the Context object that will carry student data and functions.

Step 4: Create StudentProvider.js

import React, { useState } from 'react';
import { StudentContext } from './StudentContext';

export default function StudentProvider({ children }) {
  const [students, setStudents] = useState([]);

  const addStudent = (student) => {
    setStudents(previous => [
      ...previous,
      {
        ...student,
        id: Date.now().toString(),
      },
    ]);
  };

  const deleteStudent = (id) => {
    setStudents(previous =>
      previous.filter(student => student.id !== id)
    );
  };

  return (
    <StudentContext.Provider
      value={{
        students,
        setStudents,
        addStudent,
        deleteStudent,
      }}
    >
      {children}
    </StudentContext.Provider>
  );
}

Explanation:

  • useState stores the shared student list.

  • addStudent() adds a new student.

  • deleteStudent() removes a student using the ID.

  • StudentContext.Provider shares the state and functions with child components.

Step 5: Create StudentScreen.js

import React, {
  useState,
  useEffect,
  useContext,
} from 'react';

import {
  View,
  Text,
  TextInput,
  Button,
  FlatList,
  StyleSheet,
  ActivityIndicator,
} from 'react-native';

import { StudentContext } from './StudentContext';

export default function StudentScreen() {
  const {
    students,
    setStudents,
    addStudent,
    deleteStudent,
  } = useContext(StudentContext);

  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  // Fetch sample student data using useEffect
  useEffect(() => {
    let isActive = true;

    const fetchStudents = async () => {
      try {
        setLoading(true);
        setError('');

        const response = await fetch(
          'https://jsonplaceholder.typicode.com/users'
        );

        if (!response.ok) {
          throw new Error('Unable to fetch students');
        }

        const data = await response.json();

        if (isActive) {
          const formattedStudents = data.map(student => ({
            id: String(student.id),
            name: student.name,
            email: student.email,
          }));

          setStudents(formattedStudents);
        }
      } catch (err) {
        if (isActive) {
          setError(err.message || 'Something went wrong');
        }
      } finally {
        if (isActive) {
          setLoading(false);
        }
      }
    };

    fetchStudents();

    return () => {
      isActive = false;
    };
  }, [setStudents]);

  const handleAddStudent = () => {
    if (!name.trim() || !email.trim()) {
      alert('Please enter student name and email');
      return;
    }

    addStudent({
      name: name.trim(),
      email: email.trim(),
    });

    setName('');
    setEmail('');
  };

  const renderStudent = ({ item }) => (
    <View style={styles.studentCard}>
      <View style={styles.studentInfo}>
        <Text style={styles.studentName}>
          {item.name}
        </Text>

        <Text style={styles.studentEmail}>
          {item.email}
        </Text>
      </View>

      <Button
        title="Delete"
        color="red"
        onPress={() => deleteStudent(item.id)}
      />
    </View>
  );

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#2563eb" />
        <Text>Loading students...</Text>
      </View>
    );
  }

  if (error) {
    return (
      <View style={styles.center}>
        <Text style={styles.error}>{error}</Text>
        <Text>Please check your internet connection.</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.heading}>
        Student Management
      </Text>

      <Text style={styles.subtitle}>
        Total Students: {students.length}
      </Text>

      <TextInput
        style={styles.input}
        placeholder="Enter Student Name"
        value={name}
        onChangeText={setName}
      />

      <TextInput
        style={styles.input}
        placeholder="Enter Student Email"
        value={email}
        onChangeText={setEmail}
        keyboardType="email-address"
        autoCapitalize="none"
      />

      <Button
        title="Add Student"
        onPress={handleAddStudent}
      />

      <Text style={styles.listHeading}>
        Student Records
      </Text>

      <FlatList
        data={students}
        keyExtractor={item => item.id}
        renderItem={renderStudent}
        ListEmptyComponent={
          <Text style={styles.empty}>
            No students found.
          </Text>
        }
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 18,
    backgroundColor: '#f5f7fb',
  },
  heading: {
    fontSize: 26,
    fontWeight: 'bold',
    color: '#172554',
    marginTop: 35,
  },
  subtitle: {
    fontSize: 16,
    marginVertical: 12,
    color: '#475569',
  },
  input: {
    backgroundColor: '#fff',
    borderWidth: 1,
    borderColor: '#cbd5e1',
    borderRadius: 8,
    padding: 12,
    marginBottom: 12,
  },
  listHeading: {
    fontSize: 20,
    fontWeight: 'bold',
    marginTop: 22,
    marginBottom: 12,
  },
  studentCard: {
    backgroundColor: '#fff',
    padding: 14,
    borderRadius: 8,
    marginBottom: 10,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  studentInfo: {
    flex: 1,
    marginRight: 8,
  },
  studentName: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  studentEmail: {
    fontSize: 13,
    color: '#64748b',
    marginTop: 4,
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  error: {
    color: 'red',
    fontSize: 17,
    marginBottom: 10,
  },
  empty: {
    textAlign: 'center',
    marginTop: 20,
    color: '#64748b',
  },
});

Step 6: Update App.js

import React from 'react';
import StudentProvider from './src/StudentProvider';
import StudentScreen from './src/StudentScreen';

export default function App() {
  return (
    <StudentProvider>
      <StudentScreen />
    </StudentProvider>
  );
}

Step 7: Run the Application

Run:

npx expo start

Open the app using Expo Go or an Android emulator.

You should see the Student Management screen with sample records fetched from the API.

8. Understand All Three Hooks in Our Project

useState in the Project

We used:

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(true);

These state variables store the form values and loading status.

useEffect in the Project

We used:

useEffect(() => {
  fetchStudents();
}, [setStudents]);

The effect fetches student data after the component's initial commit. The cleanup flag prevents an outdated request from updating state after the component is no longer active.

useContext in the Project

We used:

const {
  students,
  setStudents,
  addStudent,
  deleteStudent,
} = useContext(StudentContext);

This gives the screen access to shared student data and functions without passing them through multiple component levels.

9. Important Rules of Hooks

Always follow these rules:

  1. Call Hooks only at the top level of a React function component or custom Hook.

  2. Do not call Hooks inside if statements.

  3. Do not call Hooks inside loops.

  4. Do not call Hooks inside event handlers.

  5. Do not call Hooks from ordinary JavaScript functions.

  6. Keep the order of Hook calls consistent.

  7. Include the reactive values used by an effect in its dependency array.

  8. Clean up timers, subscriptions, and other resources when necessary.

Correct example:

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(count);
  }, [count]);

  return null;
}

Incorrect example:

function Example({ enabled }) {
  if (enabled) {
    const [count, setCount] = useState(0);
  }

  return null;
}

The second example breaks the Rules of Hooks because the Hook is called conditionally.

10. Common Mistakes and Solutions

Mistake 1: Updating state directly

Incorrect:

count = count + 1;

Correct:

setCount(previous => previous + 1);

Mistake 2: Missing dependencies in useEffect

Incorrect:

useEffect(() => {
  console.log(count);
}, []);

If the effect needs to respond to changes in count, include it:

useEffect(() => {
  console.log(count);
}, [count]);

Mistake 3: Forgetting cleanup

Incorrect:

useEffect(() => {
  setInterval(() => {
    console.log('Running');
  }, 1000);
}, []);

Correct:

useEffect(() => {
  const timer = setInterval(() => {
    console.log('Running');
  }, 1000);

  return () => clearInterval(timer);
}, []);

Mistake 4: Using useContext without a Provider

Make sure the component using the Context is rendered inside its matching Provider.

Mistake 5: Using useEffect for every calculation

Not every calculation needs an effect. If a value can be calculated directly from existing props or state during rendering, calculate it directly instead of creating unnecessary effect-driven state.

11. Testing Checklist

Test the application with these steps:

  • Open the application.

  • Confirm that sample students load from the API.

  • Enter a student name.

  • Enter a student email.

  • Press Add Student.

  • Confirm that the new student appears in the list.

  • Confirm that the total student count increases.

  • Delete a student.

  • Confirm that the student disappears.

  • Check the empty-list message after removing all records.

12. Limitations of This Practical Project

This is a learning project, not a production-ready database application.

  • Student records fetched from JSONPlaceholder are sample records.

  • Newly added and deleted records exist only in the app's current in-memory state.

  • Restarting the application fetches the sample API records again.

  • There is no permanent database storage or user authentication.

  • Production applications should use a proper backend API and server-side validation.

13. Frequently Asked Questions

Q1. What is useState in React Native?

useState creates a state variable inside a functional component and provides a function to update it.

Q2. What is useEffect used for?

useEffect synchronizes a component with external systems, such as APIs, timers, and subscriptions.

Q3. What is useContext?

useContext allows a component to read shared data from a matching Context Provider.

Q4. Can we use multiple Hooks in one component?

Yes. A component can use multiple Hooks, provided they follow the Rules of Hooks.

Q5. What is the difference between props and Context?

Props pass data from a parent to its child components. Context allows deeply nested components to access shared values without manually passing props through every intermediate component.

Q6. Are Hooks supported in React Native?

Yes. React Native applications use React Hooks in functional components.

14. Conclusion

React Hooks are an essential part of modern React Native development.

In this tutorial, we learned:

  • useState for managing state.

  • useEffect for synchronizing with external systems.

  • useContext for sharing application data.

  • The Rules of Hooks.

  • How to combine all three Hooks in a Student Management App.

Understanding these three Hooks will help you build cleaner, reusable, and more maintainable React Native applications.

15. Official Documentation

  1. React – useState: https://react.dev/reference/react/useState

  2. React – useEffect: https://react.dev/reference/react/useEffect

  3. React – useContext: https://react.dev/reference/react/useContext

  4. React – Rules of Hooks: https://react.dev/reference/rules/rules-of-hooks

  5. React Native – Networking: https://reactnative.dev/docs/network

  6. Expo – Create a project: https://docs.expo.dev/get-started/create-a-project/

←
Post 25 of 39
→