Popular Searches
Popular Course Categories
Popular Courses

Organizing Components, Screens, Hooks, and Services

Organizing Components, Screens, Hooks, and Services

Third-Party Integrations and Application Architecture

Organizing Components, Screens, Hooks, and Services in React Native

Course: React Native Training

Topic: Organizing Components, Screens, Hooks, and Services

Course Link: React Native Training at JustAcademy

Demo Link: Register for a Free Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Code Organization in React Native

Code organization is the process of arranging application files, folders, components, screens, hooks, and services in a structured and meaningful way. In React Native, proper code organization helps developers build applications that are easy to understand, maintain, test, debug, and extend.

As a React Native application grows, placing all application logic inside a single file becomes difficult to manage. A well-organized project separates the user interface, navigation screens, reusable logic, API communication, and shared utilities into appropriate modules.

For example, an e-commerce application may contain product listings, shopping carts, user authentication, payment processing, and order history. Each feature requires different components and logic. Organizing these responsibilities into separate files makes development more efficient.

1.1 Why Is Code Organization Important?

  • Readability: Developers can quickly understand the purpose of each file.
  • Reusability: Components and hooks can be shared across multiple screens.
  • Maintainability: Changes can be made without unnecessarily affecting unrelated features.
  • Scalability: New screens and features can be added without creating a disorganized codebase.
  • Testing: Individual components, hooks, and services can be tested separately.
  • Team collaboration: Developers can work on different features with fewer conflicts.
  • Debugging: Errors are easier to locate when responsibilities are clearly separated.

1.2 What Happens Without Proper Organization?

Consider a React Native application where the login interface, form validation, API request, navigation, and loading indicator are all implemented in one large component.

  • The component becomes difficult to read.
  • Similar code may be repeated on other screens.
  • Changes to authentication logic may accidentally affect the user interface.
  • Testing individual responsibilities becomes harder.
  • Adding new features takes more time.

Separating these responsibilities into components, screens, hooks, and services provides a cleaner solution.

2. Understanding the Main Building Blocks

A typical React Native application can be organized around four major building blocks: components, screens, hooks, and services. Each building block has a distinct responsibility.

Building Block Main Responsibility Example
Components Display reusable user interface elements. Button, Header, ProductCard
Screens Represent complete application pages. LoginScreen, HomeScreen
Hooks Encapsulate reusable stateful or lifecycle-related logic. useAuth, useProducts
Services Handle external operations and business integrations. authService, productService

2.1 How These Building Blocks Work Together

Suppose a user opens a product listing screen. The screen renders a reusable product card component. A custom hook manages the product loading state, while a product service communicates with the backend API.

  1. The screen initializes the product-loading logic.
  2. The custom hook requests product data through a service.
  3. The service communicates with the backend and returns the response.
  4. The hook exposes the products, loading state, and any error.
  5. The screen displays the appropriate interface.
  6. Reusable components render individual products.

This structure separates responsibilities while allowing each part to work together.

3. Organizing Components in React Native

Components are reusable pieces of a user interface. They may contain text, images, buttons, input fields, layouts, or other components. A component should ideally have a clear purpose and receive configurable information through props.

3.1 Types of Components

A. Basic UI Components

These components provide small, reusable interface elements.

  • CustomButton
  • AppText
  • AppInput
  • LoadingIndicator
  • Avatar

B. Composite Components

Composite components combine multiple UI elements to create a larger reusable interface.

  • ProductCard
  • ProfileHeader
  • SearchBar
  • NotificationItem
  • FormField

C. Layout Components

Layout components provide consistent spacing, alignment, or page structure.

  • ScreenContainer
  • CenteredContent
  • SectionHeader
  • SafeAreaWrapper

3.2 Creating a Reusable Button Component

Create a file named CustomButton.js inside the components folder.

import React from 'react';
import {
  Pressable,
  Text,
  StyleSheet,
  ActivityIndicator,
} from 'react-native';

export default function CustomButton({
  title,
  onPress,
  loading = false,
  disabled = false,
}) {
  const isDisabled = disabled || loading;

  return (
    <Pressable
      onPress={onPress}
      disabled={isDisabled}
      style={({ pressed }) => [
        styles.button,
        isDisabled && styles.disabled,
        pressed && !isDisabled && styles.pressed,
      ]}
      accessibilityRole="button"
      accessibilityLabel={title}
    >
      {loading ? (
        <ActivityIndicator color="#FFFFFF" />
      ) : (
        <Text style={styles.buttonText}>
          {title}
        </Text>
      )}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#2563EB',
    paddingVertical: 14,
    paddingHorizontal: 20,
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  buttonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  disabled: {
    opacity: 0.5,
  },
  pressed: {
    opacity: 0.8,
  },
});

The component accepts a title, an event handler, and optional loading and disabled states. It can be reused on login, registration, profile, and checkout screens.

3.3 Using the Reusable Component

import React from 'react';
import { View } from 'react-native';
import CustomButton from '../components/CustomButton';

export default function ExampleScreen() {
  const handleContinue = () => {
    console.log('Continue pressed');
  };

  return (
    <View>
      <CustomButton
        title="Continue"
        onPress={handleContinue}
      />
    </View>
  );
}

3.4 Best Practices for Components

  • Give every component a clear and descriptive name.
  • Keep components focused on a specific responsibility.
  • Use props to make components configurable.
  • Avoid duplicating the same UI code across screens.
  • Keep API requests out of simple presentation components when practical.
  • Use shared styles or design tokens for consistent colors and spacing.
  • Provide appropriate accessibility labels and roles.
  • Extract a component when it improves reuse, readability, or testing rather than splitting every small JSX expression automatically.

4. Organizing Screens in React Native

Screens are complete views that users access through an application's navigation system. A screen usually combines several components, manages screen-specific interactions, and coordinates data needed by that page.

4.1 Examples of Application Screens

  • HomeScreen – displays the main dashboard.
  • LoginScreen – allows users to authenticate.
  • RegisterScreen – collects registration information.
  • ProductListScreen – displays available products.
  • ProductDetailsScreen – shows information about a selected product.
  • ProfileScreen – displays account information.
  • SettingsScreen – provides application preferences.

4.2 Creating a Screen File

Create HomeScreen.js inside the screens folder.

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import CustomButton from '../components/CustomButton';

export default function HomeScreen({ navigation }) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>
        Welcome to My Application
      </Text>

      <Text style={styles.description}>
        Explore the latest products and services.
      </Text>

      <CustomButton
        title="View Products"
        onPress={() => navigation.navigate('Products')}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: '700',
    marginBottom: 12,
  },
  description: {
    fontSize: 16,
    marginBottom: 20,
  },
});

This example assumes the screen receives a compatible navigation prop and that a route named Products has been registered. The exact navigation setup depends on the routing library used by the project.

4.3 What Belongs Inside a Screen?

A screen can coordinate the following responsibilities:

  • Arrange the components required by the page.
  • Handle screen-specific user interactions.
  • Call custom hooks to access data and reusable logic.
  • Display loading, error, empty, and success states.
  • Coordinate navigation between related screens.
  • Pass data and callbacks to child components.

Large screens should not become responsible for every application feature. Extract reusable UI into components, repeated stateful logic into hooks, and external operations into services.

4.4 Screen Organization by Feature

For a larger application, screens can be grouped by feature rather than placing every screen into one folder.

src/
  features/
    auth/
      screens/
        LoginScreen.js
        RegisterScreen.js
    products/
      screens/
        ProductListScreen.js
        ProductDetailsScreen.js
    profile/
      screens/
        ProfileScreen.js

Feature-based grouping makes it easier to locate related code and reduces the need to navigate through a large collection of unrelated files.

5. Organizing Custom Hooks

React Hooks allow functional components to use state and other React features. Custom hooks are functions that combine built-in hooks and application logic into reusable units.

A custom hook typically starts with the word use, such as useAuth, useProducts, or useForm.

5.1 Why Use Custom Hooks?

  • Reuse stateful logic across multiple components.
  • Separate application logic from JSX.
  • Keep screens easier to read.
  • Centralize repeated loading and error handling patterns.
  • Make logic easier to test independently.

5.2 Creating a Custom Hook

Create a file named useCounter.js inside the hooks folder.

import { useState, useCallback } from 'react';

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

  const increment = useCallback(() => {
    setCount(current => current + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(current => current - 1);
  }, []);

  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);

  return {
    count,
    increment,
    decrement,
    reset,
  };
}

The hook encapsulates counter state and its related operations. Any component that calls this hook receives its own counter state unless the state is deliberately shared through a separate state-management solution.

5.3 Using the Custom Hook

import React from 'react';
import { View, Text } from 'react-native';
import CustomButton from '../components/CustomButton';
import useCounter from '../hooks/useCounter';

export default function CounterScreen() {
  const {
    count,
    increment,
    decrement,
    reset,
  } = useCounter(0);

  return (
    <View>
      <Text>Count: {count}</Text>

      <CustomButton
        title="Increase"
        onPress={increment}
      />

      <CustomButton
        title="Decrease"
        onPress={decrement}
      />

      <CustomButton
        title="Reset"
        onPress={reset}
      />
    </View>
  );
}

5.4 Common Types of Custom Hooks

Hook Purpose
useAuth Provides reusable authentication-related state and actions.
useProducts Loads products and manages loading or error states.
useDebounce Delays a value update until changes have paused.
useNetworkStatus Exposes network connectivity information.
useForm Manages form values, validation, and submission.
usePermissions Coordinates permission-related application logic.

5.5 Rules and Best Practices for Hooks

  • Call hooks only at the top level of React function components or custom hooks.
  • Do not call hooks inside loops, conditions, or nested ordinary functions.
  • Name custom hooks using the use prefix.
  • Keep each hook focused on a logical responsibility.
  • Include correct dependencies in useEffect, useCallback, and useMemo when they are needed.
  • Return a clear and consistent API from custom hooks.
  • Avoid putting unrelated application features into a single oversized hook.
  • Remember that custom hooks share logic, not automatically shared state between separate hook calls.

6. Organizing Services in React Native

Services contain code for communicating with external systems or performing operations that should be separated from UI components. Examples include HTTP requests, authentication APIs, payment integrations, file uploads, and analytics.

Keeping service code separate helps prevent screens from becoming tightly coupled to API endpoints and response formats.

6.1 Common Types of Services

  • API service: Makes HTTP requests to a backend.
  • Authentication service: Handles login, registration, logout, and session-related operations.
  • Storage service: Coordinates access to local or secure storage.
  • Notification service: Integrates with notification functionality.
  • Analytics service: Sends permitted application usage events.
  • Upload service: Handles uploading images or documents.

6.2 Creating an API Service

Create a file named apiClient.js inside the services folder.

const API_BASE_URL = 'https://api.example.com';

async function request(path, options = {}) {
  const response = await fetch(
    `${API_BASE_URL}${path}`,
    {
      ...options,
      headers: {
        Accept: 'application/json',
        ...options.headers,
      },
    }
  );

  if (!response.ok) {
    throw new Error(
      `Request failed with status ${response.status}`
    );
  }

  if (response.status === 204) {
    return null;
  }

  return response.json();
}

const apiClient = {
  get(path) {
    return request(path);
  },

  post(path, data) {
    return request(path, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
  },
};

export default apiClient;

This example provides a simple shared HTTP client. In a production application, additional considerations may include request timeouts, authentication headers, cancellation, response validation, retry policies, and handling non-JSON responses.

Important: Replace the example base URL with the actual backend endpoint. Never place private server secrets or confidential API keys directly in a mobile application's source code.

6.3 Creating a Feature-Specific Service

Create productService.js inside the services folder.

import apiClient from './apiClient';

const productService = {
  getProducts() {
    return apiClient.get('/products');
  },

  getProductById(id) {
    return apiClient.get(
      `/products/${encodeURIComponent(String(id))}`
    );
  },

  createProduct(productData) {
    return apiClient.post('/products', productData);
  },
};

export default productService;

The product service groups related product operations into one module. Screens and hooks can call these methods without repeating endpoint details.

6.4 Service Best Practices

  • Keep API endpoint logic out of presentation components.
  • Use descriptive service and method names.
  • Handle unsuccessful HTTP responses explicitly.
  • Keep authentication credentials and tokens out of ordinary logs.
  • Validate and safely process API responses.
  • Centralize common HTTP configuration where appropriate.
  • Separate unrelated service domains, such as authentication and products.
  • Use environment-specific configuration for development, staging, and production endpoints.
  • Do not assume that a successful HTTP status guarantees the response body has the expected structure.

7. Recommended React Native Project Folder Structure

A good folder structure depends on the size and complexity of an application. A small application may use folders based on technical responsibilities, while a larger application may group code by features.

7.1 Basic Folder Structure

MyReactNativeApp/
  src/
    components/
      CustomButton.js
      AppInput.js
      LoadingIndicator.js
      ProductCard.js
    screens/
      HomeScreen.js
      LoginScreen.js
      ProductListScreen.js
      ProfileScreen.js
    hooks/
      useAuth.js
      useProducts.js
      useCounter.js
    services/
      apiClient.js
      authService.js
      productService.js
    navigation/
      AppNavigator.js
    utils/
      formatDate.js
      validators.js
    constants/
      colors.js
      apiConfig.js
    assets/
      images/
      fonts/
  App.js
  package.json

7.2 Explanation of Each Folder

Folder Description
components/ Reusable UI elements used by multiple screens or features.
screens/ Top-level pages connected to application navigation.
hooks/ Reusable custom hooks for stateful logic.
services/ API clients and integrations with external systems.
navigation/ Route definitions, navigators, and navigation configuration.
utils/ Reusable pure helper functions and general utilities.
constants/ Shared configuration values and application constants.
assets/ Images, fonts, icons, and other bundled resources.

7.3 Feature-Based Folder Structure

For larger applications, feature-based organization keeps the files for each feature close together.

src/
  app/
    navigation/
      AppNavigator.js
  shared/
    components/
      CustomButton.js
      LoadingIndicator.js
    hooks/
      useDebounce.js
    services/
      apiClient.js
    utils/
      formatDate.js
  features/
    auth/
      components/
        LoginForm.js
      screens/
        LoginScreen.js
        RegisterScreen.js
      hooks/
        useAuth.js
      services/
        authService.js
    products/
      components/
        ProductCard.js
        ProductList.js
      screens/
        ProductListScreen.js
        ProductDetailsScreen.js
      hooks/
        useProducts.js
      services/
        productService.js
    profile/
      screens/
        ProfileScreen.js
      services/
        profileService.js

In this structure, shared code is kept in the shared folder, while feature-specific code remains inside its feature directory. This can improve discoverability as the project expands.

7.4 Which Structure Should You Choose?

Project Type Suggested Structure Reason
Small learning project Simple folders for components, screens, hooks, and services Easy to understand and set up.
Medium-sized application Shared folders plus feature folders Balances reuse with feature ownership.
Large production application Feature-based modular structure Supports team collaboration and independent feature development.

There is no single folder structure that is mandatory for every React Native project. Choose a structure that matches the application's needs and keep it consistent.

8. Connecting Screens, Hooks, Components, and Services

The following example demonstrates how the main building blocks can work together in a product listing feature.

8.1 Step 1: Create the Product Hook

Create useProducts.js inside src/hooks/, or inside the products feature's hooks folder.

import { useEffect, useState } from 'react';
import productService from '../services/productService';

export default function useProducts() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isActive = true;

    async function loadProducts() {
      try {
        setLoading(true);
        setError(null);

        const data = await productService.getProducts();

        if (isActive) {
          setProducts(data);
        }
      } catch (err) {
        if (isActive) {
          setError(err.message || 'Unable to load products');
        }
      } finally {
        if (isActive) {
          setLoading(false);
        }
      }
    }

    loadProducts();

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

  return {
    products,
    loading,
    error,
  };
}

The hook requests the product list and exposes the resulting data and status. The cleanup flag prevents state updates from this effect after its screen has unmounted. For requests that need cancellation, use an appropriate cancellation mechanism, such as an AbortController when supported by the networking environment.

8.2 Step 2: Create a Product Card

Create ProductCard.js inside the components folder.

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

export default function ProductCard({ product }) {
  return (
    <View style={styles.card}>
      <Text style={styles.name}>
        {product.name}
      </Text>

      <Text style={styles.price}>
        ${Number(product.price).toFixed(2)}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    padding: 16,
    marginBottom: 12,
    borderRadius: 8,
    backgroundColor: '#F3F4F6',
  },
  name: {
    fontSize: 17,
    fontWeight: '600',
  },
  price: {
    marginTop: 8,
    fontSize: 15,
  },
});

This component assumes each product has a valid name and numeric price. Production applications should validate data and format currency according to the selected locale and currency.

8.3 Step 3: Create the Product List Screen

Create ProductListScreen.js inside the screens folder.

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

import ProductCard from '../components/ProductCard';
import useProducts from '../hooks/useProducts';

export default function ProductListScreen() {
  const {
    products,
    loading,
    error,
  } = useProducts();

  if (loading) {
    return (
      <View>
        <ActivityIndicator size="large" />
        <Text>Loading products...</Text>
      </View>
    );
  }

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

  if (products.length === 0) {
    return <Text>No products available.</Text>;
  }

  return (
    <FlatList
      data={products}
      keyExtractor={item => String(item.id)}
      renderItem={({ item }) => (
        <ProductCard product={item} />
      )}
    />
  );
}

This screen uses a hook for data retrieval and a reusable component for presentation. It also handles loading, error, and empty states. In a production app, the error state should generally provide a retry action.

8.4 Complete Data Flow

User opens ProductListScreen
          |
          v
Screen calls useProducts()
          |
          v
Hook calls productService.getProducts()
          |
          v
Service sends API request through apiClient
          |
          v
Backend returns product data
          |
          v
Hook updates products/loading/error state
          |
          v
Screen renders ProductCard components

This approach keeps the interface, data-loading logic, and network operations separate and easier to maintain.

9. Managing Imports and Exports

Imports and exports allow files to share components, hooks, services, and utilities. Consistent module exports help prevent confusing import paths and naming errors.

9.1 Default Exports

// CustomButton.js
export default function CustomButton() {
  return null;
}

// Usage
import CustomButton from '../components/CustomButton';

9.2 Named Exports

// validators.js
export function isValidEmail(email) {
  return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);
}

export function isRequired(value) {
  return String(value ?? '').trim().length > 0;
}

// Usage
import {
  isValidEmail,
  isRequired,
} from '../utils/validators';

9.3 Using Index Files

An index file can simplify imports when a folder contains many related exports.

// components/index.js
export { default as CustomButton } from './CustomButton';
export { default as ProductCard } from './ProductCard';
export { default as LoadingIndicator } from './LoadingIndicator';

// Usage
import {
  CustomButton,
  ProductCard,
} from '../components';

Index files are optional. Use them when they improve readability, and avoid creating large export files that hide dependencies or introduce circular imports.

10. Naming Conventions and File Consistency

Consistent naming makes a project easier to navigate and helps developers recognize the purpose of each file.

File Type Example Naming Common Convention
Components CustomButton.js PascalCase
Screens LoginScreen.js PascalCase with Screen suffix
Custom hooks useAuth.js camelCase with use prefix
Services authService.js camelCase with service suffix
Utilities formatDate.js camelCase
Constants apiConfig.js Descriptive names

JavaScript and TypeScript projects may use different file extensions and naming conventions. For example, a TypeScript component might be stored in CustomButton.tsx, while a type-only module might use types.ts.

11. Organizing Styles and Design Tokens

Styles should be organized so that components remain visually consistent and easy to update. React Native commonly uses StyleSheet.create(), inline style objects for simple dynamic values, or a styling library selected by the project.

11.1 Component-Level Styles

import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
  },
  title: {
    fontSize: 22,
    fontWeight: '700',
  },
});

export default styles;

For small components, keeping styles in the same file is often convenient. For larger components, styles may be moved into a dedicated file when doing so improves readability.

11.2 Shared Design Tokens

Repeated values such as colors, spacing, and typography can be stored in a shared module.

// constants/theme.js
export const colors = {
  primary: '#2563EB',
  background: '#FFFFFF',
  text: '#111827',
  muted: '#6B7280',
  danger: '#DC2626',
};

export const spacing = {
  xs: 4,
  sm: 8,
  md: 16,
  lg: 24,
  xl: 32,
};

export const typography = {
  body: 16,
  subtitle: 18,
  heading: 24,
};

Shared tokens help maintain a consistent design system. Larger projects may organize these values into a theme provider that also supports dark mode and accessibility preferences.

12. Error Handling and Loading States

A well-organized React Native application should handle different outcomes explicitly. Screens should not assume that every request succeeds or that every response contains data.

12.1 Important UI States

  • Loading: Display a spinner, skeleton, or progress indicator while data is being retrieved.
  • Success: Display the requested content.
  • Empty: Explain that no results are available.
  • Error: Show a helpful message and, where appropriate, a retry option.
  • Offline: Explain connectivity problems or show cached data when available.

12.2 Separation of Error Responsibilities

Services should report request failures in a consistent way. Hooks can translate those failures into state that a screen can consume. Screens and components should display useful messages without exposing sensitive technical details to users.

Detailed diagnostic information may be logged through an approved logging system, but passwords, authentication tokens, and other secrets should never be included in logs.

13. Testing an Organized React Native Application

Separating components, screens, hooks, and services makes testing easier because each part has a clearer responsibility.

13.1 Component Testing

Test whether a component renders the expected content, handles user interactions, and responds correctly to props such as loading or disabled states.

13.2 Hook Testing

Test state transitions, returned values, loading behavior, error handling, and cleanup. Hook tests should verify observable behavior rather than depend on unnecessary implementation details.

13.3 Service Testing

Mock network responses and test successful requests, HTTP failures, invalid responses, and error propagation. This helps verify service behavior without requiring a live backend for every test.

13.4 Screen Testing

Test whether a screen displays loading indicators, successful results, empty states, and errors correctly. Mock hooks or service dependencies where appropriate to isolate the screen's behavior.

13.5 Suggested Testing Tools

  • Jest for running JavaScript or TypeScript tests.
  • React Native Testing Library for testing component behavior.
  • Mock Service Worker or suitable request mocks for API-related testing, depending on the application's environment.
  • TypeScript for catching many type-related mistakes before runtime when the project uses it.

14. Common Code Organization Mistakes

Mistake Why It Is a Problem Recommended Solution
Putting the entire app in one file Makes code difficult to maintain. Separate major responsibilities into modules.
Duplicating UI elements Creates inconsistent behavior and styles. Extract reusable components where beneficial.
Writing API requests in every screen Duplicates network logic. Use shared API clients and feature services.
Creating oversized custom hooks Mixes unrelated logic. Split hooks by responsibility.
Using unclear filenames Makes files difficult to locate. Adopt consistent naming conventions.
Creating too many abstractions Adds complexity without meaningful reuse. Keep simple code simple until abstraction provides value.
Mixing unrelated features in one folder Reduces discoverability as the app grows. Group related modules by feature.
Ignoring loading and error states Creates confusing user experiences. Model and render request states explicitly.
Storing secrets in source code Mobile application bundles can be inspected. Keep privileged secrets on secure server infrastructure.
Using inconsistent import patterns Makes dependencies harder to follow. Adopt predictable import and export conventions.

15. Step-by-Step Guide to Organizing an Existing Project

If an existing application has become difficult to maintain, refactor it gradually rather than rewriting the entire project at once.

  1. Inspect the current project: Identify screens, repeated UI elements, API calls, and shared logic.
  2. Choose a folder structure: Decide whether technical folders or feature-based organization best fits the application.
  3. Extract reusable components: Move repeated buttons, input fields, cards, and other UI elements into component modules.
  4. Separate screens: Keep page-level composition and navigation-related behavior in appropriate screen files.
  5. Extract custom hooks: Move repeated stateful logic into hooks with clear inputs and outputs.
  6. Create services: Centralize API requests and external integrations.
  7. Standardize shared values: Move repeated configuration and design values into appropriate shared modules.
  8. Update imports: Correct all references and remove obsolete code.
  9. Run tests: Verify that existing functionality still works.
  10. Refactor incrementally: Repeat the process feature by feature and review the result.

16. Practical Project: Organize a Product Shopping Application

Project objective: Create a simple product-shopping application with a clear separation between UI, screens, reusable hooks, and services.

16.1 Required Features

  • Home screen with a welcome message.
  • Product listing screen.
  • Reusable product cards.
  • Product API service.
  • Custom hook for retrieving products.
  • Loading and error indicators.
  • Reusable button component.
  • Navigation between the home and product listing screens.

16.2 Suggested Project Structure

src/
  components/
    CustomButton.js
    ProductCard.js
    LoadingIndicator.js
  screens/
    HomeScreen.js
    ProductListScreen.js
  hooks/
    useProducts.js
  services/
    apiClient.js
    productService.js
  navigation/
    AppNavigator.js
  constants/
    theme.js
App.js

16.3 Implementation Tasks

  1. Create the project and establish the folder structure.
  2. Build the reusable button and product card components.
  3. Implement the shared API client and product service.
  4. Create the product hook to manage fetching, loading, and errors.
  5. Build the product list screen using the hook and components.
  6. Register the screens in the chosen navigation library.
  7. Test loading, success, empty, and error scenarios.
  8. Review the code for duplicated logic and unclear responsibilities.

16.4 Expected Outcome

After completing the project, you should be able to explain how a React Native application can use reusable components, screen-level composition, custom hooks, and service modules to keep the codebase organized and maintainable.

17. Best Practices Checklist

  • Use clear, descriptive filenames.
  • Keep components focused and reusable where appropriate.
  • Keep screens responsible for page-level composition.
  • Use custom hooks for reusable stateful logic.
  • Keep external API communication inside services.
  • Use consistent imports and exports.
  • Separate shared modules from feature-specific code.
  • Handle loading, error, empty, and success states.
  • Use shared design tokens where they provide value.
  • Test components, hooks, services, and screens.
  • Avoid premature abstraction and excessive folder nesting.
  • Refactor incrementally as the application grows.

18. Interview Questions and Answers

Q1. What is code organization in React Native?

Answer: Code organization is the practice of arranging files and modules into logical structures so that application code is easier to understand, reuse, test, and maintain.

Q2. What is the difference between a component and a screen?

Answer: A component is a reusable UI element or interface building block. A screen is a complete application page that typically combines several components and is connected to navigation.

Q3. What is a custom hook?

Answer: A custom hook is a JavaScript function that uses React Hooks to encapsulate reusable stateful or lifecycle-related logic. Its name conventionally begins with use.

Q4. Why should API calls be placed in services?

Answer: Services centralize communication with external systems, reduce duplicated network code, and keep screen and component implementations focused on their primary responsibilities.

Q5. Can a component call a custom hook?

Answer: Yes. React function components can call custom hooks at their top level, following the Rules of Hooks.

Q6. Do custom hooks share state automatically?

Answer: No. Each call to a custom hook normally has its own state. Shared state requires an appropriate shared mechanism, such as Context, a state-management library, or another common state owner.

Q7. What is the difference between a hook and a service?

Answer: A hook can manage React state and lifecycle-related behavior. A service generally performs operations such as API requests and integrations without directly managing a component's React lifecycle.

Q8. What is feature-based folder organization?

Answer: It groups related files for a feature, such as authentication or products, into one directory. This improves discoverability and helps teams maintain larger applications.

Q9. Should every component have its own file?

Answer: Not necessarily. Separate files are useful when components are reusable, substantial, or independently understandable. Very small, tightly related components may remain together when that improves clarity.

Q10. How should loading and error states be handled?

Answer: Services should report failures consistently, hooks can expose loading and error state, and screens should display appropriate loading indicators, useful error messages, empty states, and retry options when needed.

Q11. What is the purpose of an API client?

Answer: An API client centralizes common HTTP behavior, such as request configuration, headers, response handling, and error processing, so feature-specific services do not repeat the same logic.

Q12. Which folder structure is best for a large React Native project?

Answer: A feature-based structure with shared components, hooks, utilities, and services is often useful for larger applications. The best structure depends on team size, project complexity, and reuse requirements.

19. Quick Revision Notes

  • Components: Build reusable user interface elements.
  • Screens: Compose the complete pages of an application.
  • Hooks: Encapsulate reusable stateful React logic.
  • Services: Handle APIs and external integrations.
  • Navigation: Defines how users move between screens.
  • Utils: Contains reusable helper functions.
  • Constants: Stores shared configuration and design values.
  • Feature-based organization: Keeps related feature files together.
  • Testing: Verifies each module's expected behavior.
  • Maintainability: Improves when responsibilities are clear and duplicated code is reduced.

20. Conclusion

Organizing components, screens, hooks, and services is an important part of building professional React Native applications. Components handle reusable interface elements, screens coordinate page-level experiences, hooks encapsulate reusable React logic, and services manage communication with APIs and other external systems.

A consistent folder structure, clear naming conventions, appropriate error handling, and focused modules make applications easier to maintain and extend. Begin with a simple structure for small projects, introduce feature-based organization when it becomes useful, and refactor incrementally as the application grows.

To learn more about React Native development, explore the React Native Training course at JustAcademy or register for a course demo.

whatsapp