Popular Searches
Popular Course Categories
Popular Courses

Lifecycle Concepts and the useEffect Hook

Lifecycle Concepts and the useEffect Hook

React Fundamentals for React Native

Lifecycle Concepts and the useEffect Hook in React Native

Course: React Native Training

Topic: Lifecycle Concepts and the useEffect Hook

Course Link: React Native Training Course

Demo Link: Register for a Course Demo

Website: JustAcademy


1. Introduction to Component Lifecycle

In React Native, a component represents a reusable part of the user interface, such as a login form, product card, profile screen, or navigation header. Throughout an application's execution, components are rendered, updated when their data changes, and sometimes removed from the interface.

The sequence of these stages is known as the component lifecycle. Understanding lifecycle concepts helps developers determine when to load data, respond to changing values, register listeners, start timers, and release resources.

Modern React Native applications commonly use functional components and Hooks to manage component behavior. The useEffect Hook is especially useful when a component needs to synchronize with an external system, such as an API, subscription, timer, or event listener.

Why lifecycle concepts are important

  • Load information when a screen requires it.
  • Respond to changes in props and state.
  • Manage timers, listeners, and subscriptions.
  • Prevent outdated asynchronous results from changing the UI.
  • Clean up resources when they are no longer required.
  • Improve reliability and maintainability in React Native applications.

2. Learning Objectives

After studying this topic, learners should be able to:

  • Explain mounting, updating, and unmounting.
  • Understand how functional components use Hooks.
  • Explain the purpose and syntax of useEffect.
  • Understand dependency arrays and effect execution.
  • Implement cleanup functions correctly.
  • Fetch API data using an effect.
  • Manage timers and event subscriptions.
  • Identify infinite render loops and stale closures.
  • Understand the difference between component lifecycle and navigation focus.
  • Build lifecycle-aware React Native screens.

3. The Three Main Lifecycle Stages

3.1 Mounting

Mounting refers to the stage when a component is first added to the rendered interface. For example, when a user opens a profile screen, React renders the component and displays its initial UI.

Typical activities associated with mounting include:

  • Initializing component state.
  • Rendering the initial interface.
  • Setting up subscriptions or connections through effects.
  • Starting data-loading operations when required.

3.2 Updating

Updating occurs when a component renders again because its state, props, or another relevant source of data changes. For example, when a user enters a search term, the search screen may render again to reflect the new input.

React calculates the updated interface and commits the necessary changes. Effects whose dependencies have changed are then synchronized with the latest values.

3.3 Unmounting

Unmounting occurs when a component is removed from the rendered interface. For example, a modal may be removed after the user closes it.

When an effect has created a resource that requires release, its cleanup function should undo that setup. Examples include clearing timers, removing listeners, and disconnecting subscriptions.

4. Class Component Lifecycle vs Functional Components

Older React applications frequently used class components with dedicated lifecycle methods. Modern React development commonly uses functional components and Hooks.

Lifecycle concept Class component method Functional component approach
Initial mounting componentDidMount() useEffect with suitable dependencies
Responding to updates componentDidUpdate() useEffect with relevant dependencies
Cleanup on removal componentWillUnmount() Cleanup function returned by an effect

These are useful conceptual comparisons, but they are not exact replacements. Effects are primarily designed to synchronize with external systems. They should not automatically be used for every calculation or state change.

5. What Is the useEffect Hook?

useEffect is a React Hook that allows a functional component to synchronize with an external system. In React Native, examples include fetching information from an API, managing timers, listening for app-state changes, and maintaining subscriptions.

The effect setup function runs after React commits the relevant render. If a dependency changes, React runs the previous cleanup function, if one exists, before setting up the effect again with the updated values.

Basic syntax

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

export default function WelcomeScreen() {
  useEffect(() => {
    console.log('Effect setup');

    return () => {
      console.log('Effect cleanup');
    };
  }, []);

  return (
    <View>
      <Text>Welcome to React Native</Text>
    </View>
  );
}

The empty dependency array means the effect has no reactive dependencies to track. In normal production behavior, it is set up after the initial commit and cleaned up when the component is removed. Development Strict Mode can perform an additional setup-and-cleanup cycle to help identify problems.

6. Understanding the useEffect Structure

useEffect(() => {
  // Setup or synchronization logic

  return () => {
    // Optional cleanup logic
  };
}, [dependencies]);
  • Setup function: Establishes the synchronization or resource needed by the component.
  • Cleanup function: Reverses or releases work established by the setup function.
  • Dependency array: Lists the reactive values that determine when the effect needs to run again.

React compares dependency values with their previous values using Object.is. The dependency list should be written inline with a constant number of dependencies.

7. useEffect with Different Dependency Arrays

7.1 Without a dependency array

useEffect(() => {
  console.log('Effect ran after a commit');
});

When the dependency array is omitted, the effect runs after every committed render. This can be useful in specific situations, but it should not be used carelessly because repeated state updates can create a render loop.

7.2 With an empty dependency array

useEffect(() => {
  console.log('Initial setup');

  return () => {
    console.log('Cleanup');
  };
}, []);

This effect has no reactive dependencies. It normally sets up after the initial commit and cleans up when the component is removed. Strict Mode can perform an extra development-only setup-and-cleanup cycle.

7.3 With a dependency array containing values

useEffect(() => {
  console.log('Current search query:', query);
}, [query]);

The effect runs after the initial commit and again when query changes. Include every reactive value used by the effect, including relevant props, state values, and variables or functions declared inside the component.

8. Dependency Array Reference Table

Dependency pattern Typical behavior Example
No array Runs after every committed render Intentional per-render synchronization
[] Initial setup and eventual cleanup Resource that does not depend on changing reactive values
[userId] Runs again when userId changes Load a selected user's profile
[query, page] Runs again when either dependency changes Update paginated search results

The dependency array should reflect what the effect actually reads. Do not remove required dependencies merely to make the effect run less often. If an effect runs too frequently, reconsider its structure and whether the effect is necessary.

9. Rendering and Effects Are Different

Rendering calculates the UI for the current props and state. Rendering should remain pure: it should not directly start timers, subscribe to external systems, or initiate network requests.

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

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

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

  return (
    <Text>Count: {count}</Text>
  );
}

The component renders the current count. The effect logs the count after a commit and whenever the count changes.

10. Fetching API Data with useEffect

Fetching data is a common React Native task. A component may need to display a loading indicator while a request is running, show the retrieved information on success, or display an error if the request fails.

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

export default function UserScreen() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  useEffect(() => {
    const controller = new AbortController();

    async function loadUser() {
      try {
        setLoading(true);
        setError('');

        const response = await fetch(
          'https://jsonplaceholder.typicode.com/users/1',
          { signal: controller.signal }
        );

        if (!response.ok) {
          throw new Error('Unable to load user information');
        }

        const result = await response.json();
        setUser(result);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message || 'Something went wrong');
        }
      } finally {
        if (!controller.signal.aborted) {
          setLoading(false);
        }
      }
    }

    loadUser();

    return () => {
      controller.abort();
    };
  }, []);

  if (loading) {
    return <ActivityIndicator size="large" />;
  }

  if (error) {
    return <Text>Error: {error}</Text>;
  }

  if (!user) {
    return <Text>No user data available.</Text>;
  }

  return (
    <View>
      <Text>Name: {user.name}</Text>
      <Text>Email: {user.email}</Text>
    </View>
  );
}

Step-by-step explanation

  1. The component creates state variables for user data, loading, and error status.
  2. The effect creates an AbortController to support request cancellation.
  3. An inner asynchronous function sends the API request.
  4. The response status is checked before parsing the response body.
  5. Successful data is stored in state.
  6. Errors are handled separately from successful responses.
  7. The cleanup function aborts the request if the effect is cleaned up.
  8. The interface displays a loading indicator, error message, or user details.

For more advanced applications, consider a dedicated data-fetching layer that supports caching, retries, and request deduplication. Verify cancellation support in the runtime and libraries used by your project.

11. Why an Effect Callback Should Not Be async

The function passed directly to useEffect should not be declared async. An asynchronous function returns a Promise, whereas React expects the effect setup function to return nothing or a cleanup function.

Incorrect approach:

useEffect(async () => {
  const response = await fetch(url);
  const data = await response.json();
  setData(data);
}, [url]);

Recommended approach:

useEffect(() => {
  async function loadData() {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error('Request failed');
    }

    const result = await response.json();
    setData(result);
  }

  loadData().catch(error => {
    console.error(error);
  });
}, [url]);

For production code, also manage loading state, errors, and cleanup or stale-result protection. An inner asynchronous function allows the outer effect to return a cleanup function when necessary.

12. Cleanup Functions

A cleanup function reverses or releases resources established by an effect. React runs cleanup before setting up the effect again when its dependencies change, and when the component is removed.

Common resources requiring cleanup include:

  • Timers created with setInterval or setTimeout.
  • Event listeners and subscriptions.
  • WebSocket connections.
  • Network requests that support cancellation.
  • External resources that require explicit disposal.

Example: Cleaning up a timer

useEffect(() => {
  const timerId = setInterval(() => {
    console.log('Timer is running');
  }, 1000);

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

The cleanup function calls clearInterval so that the timer stops when it is no longer required.

13. Building a Countdown Timer

This example demonstrates how a timer can be synchronized with component state.

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

export default function CountdownScreen() {
  const [seconds, setSeconds] = useState(10);
  const [running, setRunning] = useState(false);

  useEffect(() => {
    if (!running || seconds <= 0) {
      return;
    }

    const timerId = setInterval(() => {
      setSeconds(previous => Math.max(previous - 1, 0));
    }, 1000);

    return () => clearInterval(timerId);
  }, [running, seconds]);

  useEffect(() => {
    if (seconds === 0) {
      setRunning(false);
    }
  }, [seconds]);

  return (
    <View>
      <Text>Seconds remaining: {seconds}</Text>
      <Button
        title={running ? 'Pause' : 'Start'}
        onPress={() => setRunning(previous => !previous)}
      />
      <Button
        title="Reset"
        onPress={() => {
          setRunning(false);
          setSeconds(10);
        }}
      />
    </View>
  );
}

Because seconds is a dependency, the effect cleans up and recreates the interval whenever the value changes. This demonstrates cleanup correctly, although a more refined timer can keep one interval active while running and use functional state updates.

14. Improving a Timer with Functional State Updates

A functional state update calculates the next value using the previous state. This is particularly useful for timers, counters, and other asynchronous callbacks.

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

export default function StopwatchScreen() {
  const [seconds, setSeconds] = useState(0);
  const [running, setRunning] = useState(false);

  useEffect(() => {
    if (!running) {
      return;
    }

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

    return () => clearInterval(timerId);
  }, [running]);

  return (
    <View>
      <Text>Elapsed time: {seconds} seconds</Text>
      <Button
        title={running ? 'Pause' : 'Start'}
        onPress={() => setRunning(previous => !previous)}
      />
      <Button
        title="Reset"
        onPress={() => {
          setRunning(false);
          setSeconds(0);
        }}
      />
    </View>
  );
}

The effect depends only on running. When the timer starts, the interval is created. When the timer pauses or the component is removed, the interval is cleared. The functional update reads the latest state without requiring seconds in the dependency array.

15. React Native AppState and Lifecycle

React Native provides the AppState API to detect whether the application is active, in the background, or inactive. This is different from a component's mounting or unmounting lifecycle.

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

export default function AppStateScreen() {
  const [appState, setAppState] = useState(
    AppState.currentState
  );

  useEffect(() => {
    const subscription = AppState.addEventListener(
      'change',
      nextState => {
        setAppState(nextState);
      }
    );

    return () => {
      subscription.remove();
    };
  }, []);

  return (
    <View>
      <Text>Current app state: {String(appState)}</Text>
    </View>
  );
}

Explanation

  • AppState.currentState provides the current app state when available.
  • AppState.addEventListener() registers a listener for app-state changes.
  • The callback updates the state displayed by the component.
  • subscription.remove() unregisters the listener during cleanup.

Common app states include active and background; iOS can also report inactive. The initial state may be temporarily unavailable in some environments. Consult the official React Native AppState documentation for platform-specific behavior.

16. Effects and Navigation Lifecycle

React Native applications often use navigation libraries to move between screens. Depending on the navigator configuration, a screen may remain mounted even when another screen is visible.

Therefore, an effect with an empty dependency array does not necessarily run every time a user revisits a screen. If data must refresh whenever a screen gains focus, use the focus lifecycle supported by the navigation library.

Examples of navigation-related requirements

  • Refresh a list whenever the user returns to it.
  • Subscribe to updates while a screen is focused.
  • Stop screen-specific activity when the screen loses focus.
  • Remove subscriptions when they are no longer needed.

Choose the appropriate lifecycle mechanism based on whether the requirement concerns component mounting, dependency changes, or navigation focus.

17. Preventing Infinite Render Loops

An infinite render loop may occur when an effect updates state and the update changes one of the effect's dependencies, causing the effect to run repeatedly.

Problematic example:

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

useEffect(() => {
  setCount(count + 1);
}, [count]);

The effect increments the count whenever the count changes, creating a repeated cycle.

Better approach for a button counter:

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

return (
  <Button
    title={`Count: ${count}`}
    onPress={() => setCount(previous => previous + 1)}
  />
);

Update state directly in an event handler when the change is caused by a user interaction. Use effects when synchronization with an external system is actually required.

18. Understanding Stale Closures

A closure captures values from the render in which a callback was created. A timer or subscription may continue using an old value if it does not receive updated dependencies or use an appropriate state updater.

Potentially problematic example:

useEffect(() => {
  const timerId = setInterval(() => {
    setCount(count + 1);
  }, 1000);

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

The interval callback captures the initial value of count. It may repeatedly attempt to set the same next value.

Corrected example:

useEffect(() => {
  const timerId = setInterval(() => {
    setCount(previous => previous + 1);
  }, 1000);

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

The functional updater calculates the next count from the latest state, preventing this stale-state problem.

19. Avoiding Unnecessary Effects

Not every calculation or state update requires useEffect. If a value can be calculated directly from existing props or state, deriving it during rendering is usually simpler.

Unnecessary approach:

const [firstName, setFirstName] = useState('Asha');
const [lastName, setLastName] = useState('Sharma');
const [fullName, setFullName] = useState('');

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

Simpler approach:

const [firstName, setFirstName] = useState('Asha');
const [lastName, setLastName] = useState('Sharma');

const fullName = `${firstName} ${lastName}`;

The second approach avoids duplicated state and an unnecessary effect.

20. Managing Object and Function Dependencies

Objects and functions created inside a component can receive new references on every render. If an effect depends on one of those references, it may run more often than intended.

Example of an avoidable dependency:

const options = {
  category,
  sortOrder
};

useEffect(() => {
  loadProducts(options);
}, [options]);

A new options object is created on every render. A simpler approach is to create it inside the effect and depend on the actual values used.

useEffect(() => {
  const options = {
    category,
    sortOrder
  };

  loadProducts(options);
}, [category, sortOrder]);

This makes the dependencies clearer. For asynchronous requests, also consider cancellation or ignoring outdated results when parameters change quickly.

21. Fetching Data When a Prop Changes

Suppose a profile screen receives a userId prop. When the identifier changes, the screen should load the corresponding user's details.

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

export default function ProfileScreen({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState('');

  useEffect(() => {
    let ignore = false;

    async function loadUser() {
      try {
        setError('');
        setUser(null);

        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`
        );

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

        const result = await response.json();

        if (!ignore) {
          setUser(result);
        }
      } catch (err) {
        if (!ignore) {
          setError(err.message || 'An error occurred');
        }
      }
    }

    loadUser();

    return () => {
      ignore = true;
    };
  }, [userId]);

  if (error) {
    return <Text>{error}</Text>;
  }

  if (!user) {
    return <Text>Loading profile...</Text>;
  }

  return (
    <View>
      <Text>Name: {user.name}</Text>
      <Text>Email: {user.email}</Text>
    </View>
  );
}

The effect runs when userId changes. The cleanup function prevents an older request from applying its result after a newer identifier has been selected. For production applications, request cancellation and a reusable data-fetching layer may be appropriate.

22. Managing Event Listeners

Event listeners should be removed when the component no longer needs them. Otherwise, duplicate listeners or unwanted callbacks may occur.

useEffect(() => {
  function handleRefresh() {
    console.log('Refresh event received');
  }

  const subscription = eventEmitter.addListener(
    'refresh',
    handleRefresh
  );

  return () => {
    subscription.remove();
  };
}, []);

This example assumes an event emitter whose API returns a subscription with a remove() method. Other libraries may use different registration and cleanup methods, so follow the API of the library being used.

23. Rules of Hooks

The Rules of Hooks ensure that React can consistently associate Hook calls with the correct component state and behavior.

  • Call Hooks at the top level of functional components or custom Hooks.
  • Do not call Hooks inside loops, conditions, or nested functions.
  • Do not call Hooks from ordinary JavaScript functions that are not components or custom Hooks.
  • Declare all necessary reactive dependencies.
  • Keep rendering logic pure.

Incorrect:

if (isLoggedIn) {
  useEffect(() => {
    console.log('Logged in');
  }, []);
}

Correct:

useEffect(() => {
  if (isLoggedIn) {
    console.log('Logged in');
  }
}, [isLoggedIn]);

The Hook is called unconditionally at the top level, and the condition is placed inside the effect.

24. Common Mistakes and Troubleshooting

Mistake Problem Recommended solution
Missing dependencies The effect can use outdated values. Include every required reactive dependency.
Omitting the array unintentionally The effect runs after every committed render. Use the appropriate dependency array.
Unconditional state updates in an effect Can cause an infinite render loop. Reconsider whether the effect is necessary.
Missing timer cleanup Timers may continue unnecessarily. Call clearInterval or clearTimeout.
Missing listener cleanup Duplicate or unwanted callbacks may occur. Remove the subscription in cleanup.
Async effect callback Returns a Promise instead of a cleanup function. Use an inner async function.
Assuming a screen always remounts Some navigation setups keep screens mounted. Use the relevant navigation focus lifecycle.
Outdated API responses An older response can overwrite newer data. Cancel old requests or ignore their results.
Unstable object dependencies The effect may rerun unnecessarily. Depend on relevant values and restructure the effect.
Unexpected development logs Strict Mode may run an extra setup-and-cleanup cycle. Ensure setup and cleanup are safe to repeat.

25. Practical Project: User Profile Loader

Project objective: Create a React Native screen that loads a user profile from an API and displays loading, success, and error states.

Project requirements

  • Create a functional component named UserProfileScreen.
  • Use useState for profile data, loading status, and error messages.
  • Use useEffect to load data when the user identifier changes.
  • Validate the HTTP response before parsing JSON.
  • Prevent outdated requests from overwriting the current result.
  • Show an activity indicator while data is loading.
  • Display an error message when a request fails.
  • Display user information after a successful request.
  • Implement appropriate cleanup logic.

Suggested folder structure

src/
  components/
    LoadingView.js
    ErrorView.js
  screens/
    UserProfileScreen.js
  services/
    userService.js

Development steps

  1. Initialize a React Native project using your selected development tooling.
  2. Create the user profile screen.
  3. Define the required state variables.
  4. Implement an effect that responds to the user identifier.
  5. Write the asynchronous API request.
  6. Add loading and error handling.
  7. Test what happens when the identifier changes quickly.
  8. Verify that outdated requests cannot overwrite newer results.
  9. Test the screen on an emulator or physical device.

26. Practice Exercises

  1. Basic effect: Log a message after the initial commit and explain the additional development Strict Mode cycle.
  2. Dependency tracking: Build a search input and respond to changes in the query.
  3. Timer: Create a stopwatch with start, pause, and reset controls.
  4. API request: Fetch a list of users and display their names.
  5. Error handling: Add loading and error states to an API-driven screen.
  6. Cleanup: Register an event listener and remove it when it is no longer required.
  7. App state: Display the current application state with the React Native AppState API.
  8. Dependency debugging: Find and correct an effect that causes repeated state updates.
  9. Navigation: Refresh a screen when it regains focus using your navigation library.
  10. Code review: Replace an unnecessary effect with a value calculated directly from props or state.

27. Interview Questions and Answers

Q1. What is a component lifecycle?

Answer: It describes the stages a component goes through, commonly called mounting, updating, and unmounting.

Q2. What is the useEffect Hook?

Answer: It allows functional components to synchronize with external systems, such as APIs, timers, and subscriptions.

Q3. What does an empty dependency array mean?

Answer: It means the effect has no reactive dependencies to track. It normally sets up after the initial commit and cleans up when appropriate. Development Strict Mode may run an additional setup-and-cleanup cycle.

Q4. What happens when no dependency array is provided?

Answer: The effect runs after every committed render.

Q5. When does an effect with dependencies run again?

Answer: When one or more dependency values differ from their previous values after a commit.

Q6. What is a cleanup function?

Answer: It reverses or releases resources established by the effect setup function.

Q7. Why should timers be cleaned up?

Answer: To prevent timers from continuing when they are no longer required.

Q8. Why should an effect callback not be async?

Answer: An async function returns a Promise, but React expects the effect setup function to return nothing or a cleanup function.

Q9. What is an infinite effect loop?

Answer: It is a repeated cycle where an effect updates state, that update changes a dependency, and the effect runs again.

Q10. What is a stale closure?

Answer: It occurs when a callback continues using values captured from an older render.

Q11. Why is the dependency array important?

Answer: It tells React which reactive values the effect uses and when synchronization should be repeated.

Q12. Does useEffect run every time a screen becomes visible?

Answer: Not necessarily. A screen can remain mounted when another screen is displayed. Navigation focus behavior may need to be handled separately.

Q13. Why can an effect run twice in development?

Answer: React Strict Mode can perform an additional setup-and-cleanup cycle to reveal problems in effect logic.

Q14. Should every state change be handled with useEffect?

Answer: No. User-triggered updates generally belong in event handlers, and derived values can often be calculated during rendering.

Q15. How can outdated API requests be handled?

Answer: Cancel requests when supported or ignore their results during cleanup so that old responses cannot overwrite newer data.

28. Quick Revision Checklist

  • Understand mounting, updating, and unmounting.
  • Know the purpose of useEffect.
  • Understand empty, populated, and omitted dependency arrays.
  • Include the correct reactive dependencies.
  • Clean up timers, listeners, and subscriptions.
  • Handle API loading, success, and error states.
  • Use functional state updates when appropriate.
  • Avoid unnecessary effects and duplicated state.
  • Prevent infinite loops and stale closures.
  • Understand the difference between component lifecycle and navigation focus.
  • Test effects under development Strict Mode.

29. Best Practices

  • Keep each effect focused on a specific synchronization task.
  • Make cleanup reverse the work performed by setup.
  • Use React Hooks lint rules to identify missing dependencies.
  • Do not suppress dependency warnings without understanding the cause.
  • Handle request failures and outdated responses deliberately.
  • Use functional state updates when the next value depends on the previous state.
  • Keep side effects out of rendering.
  • Calculate derived values directly instead of duplicating them in state.
  • Distinguish component mounting from navigation focus.
  • Test behavior on the target iOS and Android environments.

30. Official Learning Resources

31. Conclusion

Lifecycle concepts help React Native developers understand when components are rendered, updated, and removed. The useEffect Hook provides a structured way to synchronize components with external systems and respond to changes in reactive values.

By managing dependencies carefully, handling asynchronous operations correctly, and implementing cleanup functions, developers can build reliable screens and avoid common problems such as stale closures, repeated requests, and unnecessary resource usage.

Continue practising with API-driven screens, timers, event listeners, and navigation-aware components. Explore the React Native Training course at JustAcademy, visit the JustAcademy website, or register for a course demo.

whatsapp