# Component Design Standards for React Native
This document outlines the coding standards for React Native component design. It provides guidance on creating reusable, maintainable, and performant components. Following these standards ensures code consistency, reduces bugs, and enhances collaboration within development teams. The standards are based on the latest React Native best practices and aim to promote a modern, efficient development workflow.
## 1. Component Architecture and Structure
### 1.1. Component Types
**Do This:**
* Favor functional components with React Hooks over class components for new development. Function components are more concise, readable, and encourage better code organization.
* Use class components only when integrating with older codebases or when very specific lifecycle methods are absolutely necessary and cannot be emulated with hooks (which is rare).
**Don't Do This:**
* Rely heavily on class components for new implementations. This leads to verbose code and potential performance overhead.
* Mix functional and class components unnecessarily, as this introduces inconsistency and makes the codebase harder to understand.
**Why:** Functional components with hooks promote better code organization, readability, and testability. They also align with the modern React paradigm, simplifying state management and side-effect handling.
**Example:**
"""jsx
// Functional Component with Hooks (Preferred)
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
const MyComponent = ({ text }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
// Simulate loading data
setTimeout(() => {
setLoading(false);
}, 1000);
}, []);
if (loading) {
return (
);
}
return (
{text}
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
text: {
fontSize: 16,
},
});
export default MyComponent;
// Class Component (Avoid for new development)
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
class MyClassComponent extends Component {
constructor(props) {
super(props);
this.state = {
message: 'Hello from Class Component!',
};
}
render() {
return (
{this.state.message}
);
}
}
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
text: {
fontSize: 16,
},
});
export default MyClassComponent;
"""
### 1.2. Component Composition
**Do This:**
* Favor component composition over deep inheritance. Create small, focused components that can be composed together to form more complex UIs.
* Use React's "children" prop or render props to pass content between components. Consider using render callbacks as needed.
**Don't Do This:**
* Create large, monolithic components that handle too much logic.
* Rely on deep inheritance hierarchies, as this makes code harder to understand and maintain.
**Why:** Component composition promotes reusability, modularity, and testability. It also aligns with the React philosophy of building UIs from small, independent components.
**Example:**
"""jsx
// Composed Components
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
// Reusable Button Component
const Button = ({ title, onPress, style }) => (
{title}
);
// Card Component using Button
const Card = ({ children, title }) => (
{title}
{children}
);
const MyScreen = () => (
This is content inside the card.
alert('Button Pressed!')} />
);
const styles = StyleSheet.create({
card: {
backgroundColor: 'white',
padding: 16,
margin: 8,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.23,
shadowRadius: 2.62,
elevation: 4,
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
},
button: {
backgroundColor: '#2196F3',
padding: 10,
borderRadius: 5,
marginTop: 10,
},
buttonText: {
color: 'white',
textAlign: 'center',
},
});
//Example of using a "render props" pattern
const DataProvider = ({ url, render }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(jsonData => {
setData(jsonData);
setLoading(false);
})
.catch(error => {
console.error("Error fetching data:", error);
setLoading(false); //Ensure loading is set to false even on error
});
}, [url]);
return loading ? Loading data... : render(data);
};
// Usage:
const UserList = () => (
(
{users.map(user => (
{user.name}
))}
)}
/>
);
export default MyScreen;
"""
### 1.3. Folder Structure
**Do This:**
* Organize components based on features or modules. Create separate folders for reusable components, screens, and utility functions.
* Use a consistent naming convention for files and folders.
**Don't Do This:**
* Place all components in a single folder.
* Use inconsistent naming conventions.
**Why:** A well-organized folder structure improves code discoverability, maintainability, and scalability.
**Example:**
"""
src/
├── components/ # Reusable components
│ ├── Button/
│ │ ├── Button.js # Component implementation
│ │ └── Button.styles.js # Component styles
│ ├── Card/
│ │ ├── Card.js
│ │ └── Card.styles.js
├── screens/ # Application screens
│ ├── HomeScreen/
│ │ ├── HomeScreen.js
│ │ └── HomeScreen.styles.js
│ ├── ProfileScreen/
│ │ ├── ProfileScreen.js
│ │ └── ProfileScreen.styles.js
├── utils/ # Utility functions
│ ├── api.js
│ └── helpers.js
"""
## 2. Component Implementation Details
### 2.1. Props Validation
**Do This:**
* Use "PropTypes" or TypeScript to define the expected types and shapes of component props. This helps catch errors early and improves code documentation.
* Make use of default prop values as much as possible.
**Don't Do This:**
* Skip prop validation.
* Use "any" types recklessly in TypeScript.
**Why:** Prop validation improves code reliability and helps prevent unexpected errors.
**Example:**
"""jsx
// Using PropTypes
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
const MyComponent = ({ name, age, isAdmin, style }) => (
Name: {name}
Age: {age}
Is Admin: {isAdmin ? 'Yes' : 'No'}
);
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isAdmin: PropTypes.bool,
style: PropTypes.object,
};
MyComponent.defaultProps = {
age: 18,
isAdmin: false,
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
text: {
fontSize: 16,
},
});
export default MyComponent;
// Using TypeScript
interface Props {
name: string;
age?: number;
isAdmin?: boolean;
style?: StyleProp;
}
const MyTypescriptComponent: React.FC = ({ name, age = 18, isAdmin = false, style }) => (
Name: {name}
Age: {age}
Is Admin: {isAdmin ? 'Yes' : 'No'}
);
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
text: {
fontSize: 16,
},
});
export default MyTypescriptComponent;
"""
### 2.2. State Management
**Do This:**
* Use React's "useState" hook for local component state.
* Use Context API, Redux, Zustand, or similar libraries for application-wide state management.
* Consider using "useReducer" hook for more complex state logic within a component.
**Don't Do This:**
* Overuse global state management for local component state.
* Mutate state directly (e.g., "this.state.value = newValue"). Always use "setState" or the state updater function provided by "useState".
**Why:** Proper state management ensures predictable data flow and simplifies debugging. Avoid unnecessary re-renders that can occur with poorly managed state.
**Example:**
"""jsx
// Using useState hook
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const CounterComponent = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1); // Functional update form
};
const decrement = () => {
setCount(count - 1);
};
return (
Count: {count}
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
text: {
fontSize: 16,
},
});
// Using useReducer hook
import React, { useReducer } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
const CounterWithReducer = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
Count: {state.count}
dispatch({ type: 'INCREMENT' })} />
dispatch({ type: 'DECREMENT' })} />
);
};
"""
### 2.3. Styling
**Do This:**
* Use React Native's "StyleSheet" API for defining styles. This helps to enforce consistency and improve performance.
* Keep styles close to the component they are styling (co-location).
* Leverage style inheritance and composition to avoid duplication.
* Use themes and style variables for consistent look and feel.
**Don't Do This:**
* Use inline styles excessively, as this makes code harder to read and maintain.
* Hardcode style values throughout the application.
**Why:** Consistent styling improves the user experience and makes the application easier to maintain.
**Example:**
"""jsx
// Using StyleSheet
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const MyComponent = ({ text }) => (
{text}
);
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
alignItems: 'center',
},
text: {
fontSize: 16,
color: '#333',
},
});
// Example of Themes:
const theme = {
colors: {
primary: '#2196F3',
secondary: '#607D8B',
text: '#333',
background: '#f0f0f0'
},
spacing: {
small: 8,
medium: 16,
large: 24
},
typography: {
fontSize: {
small: 14,
medium: 16,
large: 18
}
}
};
//In a separate file, create a context
import React from 'react';
const ThemeContext = React.createContext(theme);
export default ThemeContext;
//A custom hook to consume the theme:
import { useContext } from 'react';
import ThemeContext from './ThemeContext';
const useTheme = () => useContext(ThemeContext);
export default useTheme;
//Then in a component
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import useTheme from './useTheme'; //Import the custom hook
const ThemedComponent = () => {
const theme = useTheme(); //Consume the theme
return (
Themed Text
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
borderRadius: 8,
alignItems: 'center',
},
text: {
fontSize: 16,
},
});
export default ThemedComponent;
export default MyComponent;
"""
### 2.4. Handling Asynchronous Operations
**Do This:**
* Use "async/await" syntax for handling asynchronous operations.
* Implement proper error handling using "try/catch" blocks.
* Show loading indicators while waiting for asynchronous operations to complete.
**Don't Do This:**
* Use callbacks excessively, as this can lead to callback hell.
* Ignore potential errors during asynchronous operations.
**Why:** Proper handling of asynchronous operations ensures a smooth and responsive user experience.
**Example:**
"""jsx
// Using async/await
import React, { useState, useEffect } from 'react';
import { View, Text, Button, ActivityIndicator, StyleSheet } from 'react-native';
const DataFetchingComponent = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error("HTTP error! status: ${response.status}");
}
const json = await response.json();
setData(json);
} catch (e) {
setError(e);
console.error("Fetching error:", e); // Consider using a logging service in production
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
);
}
if (error) {
return (
Error: {error.message}
);
}
return (
Title: {data.title}
Completed: {data.completed ? 'Yes' : 'No'}
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
alignItems: 'center',
},
text: {
fontSize: 16,
},
});
export default DataFetchingComponent;
"""
## 3. Performance Optimization
### 3.1. Memoization
**Do This:**
* Use "React.memo" to memoize functional components and prevent unnecessary re-renders.
* Use "useMemo" and "useCallback" hooks to memoize values and functions passed as props.
**Don't Do This:**
* Memoize components indiscriminately, as this can add unnecessary overhead.
* Pass new object literals or inline functions as props to memoized components.
**Why:** Memoization can improve performance by preventing unnecessary re-renders of components, particularly when dealing with complex UIs or expensive calculations.
**Example:**
"""jsx
// Using React.memo
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const MyComponent = ({ text }) => {
console.log('MyComponent rendered'); // For debugging render frequency
return (
{text}
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
margin: 10,
},
text: {
fontSize: 16,
},
});
const MemoizedComponent = React.memo(MyComponent); //Memoize the component
export default MemoizedComponent;
// Using useCallback and useMemo
import React, { useState, useCallback, useMemo } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const ExpensiveComponent = React.memo(({ data, onClick }) => {
console.log('ExpensiveComponent rendered');
return (
);
});
const MyParentComponent = () => {
const [count, setCount] = useState(0);
const data = useMemo(() => ({ value: count }), [count]); //Memoize data
const handleClick = useCallback(() => { // Memoize the callback
alert("Clicked item ${count}");
}, [count]);
return (
Count: {count}
setCount(count + 1)} />
);
};
"""
### 3.2. Optimizing List Rendering
**Do This:**
* Use "FlatList" or "SectionList" components for rendering large lists. These components virtualize the list and only render items that are currently visible on the screen.
* Provide a unique "key" prop for each item in the list.
* Implement "getItemLayout" prop to improve scrolling performance.
**Don't Do This:**
* Use "ScrollView" for rendering large lists, as this can cause performance issues.
* Use the index as the key prop.
**Why:** "FlatList" and "SectionList" optimize list rendering by virtualizing the list and only rendering items that are currently visible on the screen.
**Example:**
"""jsx
// Using FlatList
import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
const data = Array.from({ length: 100 }, (_, i) => ({ id: i.toString(), value: "Item ${i + 1}" }));
const Item = ({ title }) => (
{title}
);
const MyListComponent = () => {
const renderItem = ({ item }) => (
);
return (
item.id}
getItemLayout={(data, index) => ({length: 50, offset: 50 * index, index})} //Example, assuming item height is fixed at 50
/>
);
};
const styles = StyleSheet.create({
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 16,
},
});
export default MyListComponent;
"""
### 3.3 Image Optimization
**Do This:**
* Use optimized image formats (e.g., WebP) to reduce image size.
* Use "Image" component's "resizeMode" prop effectively (cover, contain, stretch, repeat, center).
* Consider using a library like "react-native-fast-image" for improved image caching and performance.
**Don't Do This:**
* Use large, unoptimized images.
* Load images unnecessarily.
**Why:** Optimized images reduce the application's footprint, improve loading times, and enhance the user experience.
**Example:**
"""jsx
// Using Image component
import React from 'react';
import { Image, View, StyleSheet } from 'react-native';
const MyImageComponent = () => (
);
const styles = StyleSheet.create({
container: {
padding: 20,
},
image: {
width: 150,
height: 150,
},
});
export default MyImageComponent;
//Using FastImage
import FastImage from 'react-native-fast-image'
const FastImageComponent = () => (
)
"""
## 4. Security Best Practices
### 4.1. Data Validation and Sanitization
**Do This:**
* Validate and sanitize all user inputs to prevent injection attacks (e.g., SQL injection, XSS).
* Use secure storage mechanisms for storing sensitive data (e.g., passwords, API keys).
**Don't Do This:**
* Trust user inputs without validation.
* Store sensitive data in plain text.
**Why:** Data validation and sanitization prevent malicious code from being injected into the application. Secure storage protects sensitive data from unauthorized access.
### 4.2. Secure Communication
**Do This:**
* Use HTTPS for all network communication to encrypt data in transit.
* Implement proper authentication and authorization mechanisms to protect API endpoints.
**Don't Do This:**
* Use HTTP for transmitting sensitive data.
* Expose API endpoints without authentication.
**Why:** Secure communication protects data from eavesdropping and tampering. Proper authentication and authorization prevent unauthorized access to API endpoints.
### 4.3. Dependency Management
**Do This:**
* Keep dependencies up-to-date to patch security vulnerabilities.
* Review dependencies for potential security risks.
* Use a package manager (e.g., npm, yarn) to manage dependencies.
**Don't Do This:**
* Use outdated dependencies.
* Install dependencies from untrusted sources.
**Why:** Up-to-date dependencies reduce the risk of security vulnerabilities. Reviewing dependencies helps to identify and mitigate potential security risks.
## 5. Testing
### 5.1. Unit Testing
**Do This:**
* Write unit tests for individual components and functions to ensure they work as expected.
* Use a testing framework (e.g., Jest, Mocha) to run unit tests.
**Don't Do This:**
* Skip unit testing.
* Write tests that are too complex or tightly coupled to the implementation.
**Why:** Unit tests provide a safety net for code changes and help to prevent regressions.
### 5.2. Integration Testing
**Do This:**
* Write integration tests to ensure that components work together correctly.
* Use a testing framework (e.g., Detox, Appium) to run integration tests.
**Don't Do This:**
* Skip integration testing.
* Write tests that are too brittle or rely on specific implementation details.
**Why:** Integration tests ensure that the different parts of the application work together correctly.
### 5.3 Mocking
**Do This:**
* Mock external API calls
* Mock native dependencies such as file system access or bluetooth
* Use mocking libraries such as jest.mock or react-native-reanimated mocks to reduce build times and improve test isolation.
**Don't Do This:**
* Skip mocking key dependencies that will block testing.
* Over mock components that makes tests unnecessary brittle.
**Why:** Mocking dependencies decreases test times and improves granularity.
By adhering to these component design standards, development teams can create React Native applications that are maintainable, performant, secure, and testable. These guidelines are intended to be flexible and adaptable to the specific needs of individual projects.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Tooling and Ecosystem Standards for React Native This document outlines coding standards specifically related to tooling and ecosystem choices within React Native projects. The emphasis is on selecting, configuring, and utilizing tools and libraries that enhance development speed, code quality, maintainability, and performance in modern React Native applications. ## 1. Project Setup and Tooling ### 1.1. Package Management **Standard:** Use Yarn or npm for package management. Prefer Yarn 3+ with Plug'n'Play (PnP) for improved performance and dependency management. **Do This:** """bash # Using Yarn yarn add react react-native # Using npm npm install react react-native """ **Don't Do This:** * Mixing "yarn" and "npm" in the same project. * Committing "node_modules" directory. **Why:** Consistent package management ensures reproducible builds and avoids dependency conflicts. Yarn PnP offers faster installations and a cleaner project structure by eliminating the "node_modules" directory. **Example (Yarn PnP):** 1. Enable PnP by setting "nodeLinker: pnp" in ".yarnrc.yml". 2. Install dependencies: "yarn install" ### 1.2. Development Environment **Standard:** Use VS Code or a similar IDE with React Native specific extensions. **Do This:** Install the following VS Code extensions: * ESLint * Prettier * React Native Tools (if using VS Code) * Debugger for Chrome/Edge (for web debugging of React Native components) **Don't Do This:** * Using a basic text editor without linting, formatting, or debugging capabilities. **Why:** IDE extensions provide real-time feedback on code quality, formatting, and potential errors, significantly improving developer productivity. **Example (VS Code .vscode/settings.json):** """json { "editor.formatOnSave": true, "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact" ], "files.eol": "\n", //Enforce consistent line endings "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "prettier.requireConfig": true // Enforce project Prettier config } """ ### 1.3. CLI Tools **Standard:** Use "npx" to execute locally installed CLI tools. **Do This:** """bash npx react-native init MyApp npx eslint . --fix """ **Don't Do This:** * Installing CLI tools globally unless strictly necessary. **Why:** "npx" ensures that you are using the version of the tool specified in your "package.json", avoiding version conflicts and inconsistencies across different environments. ### 1.4. TypeScript **Standard:** Strongly consider using TypeScript for type safety, improved code maintainability, and enhanced developer experience. **Do This:** * Initialize a new React Native project with TypeScript ("npx react-native init MyApp --template react-native-template-typescript"). * Gradually migrate existing JavaScript projects to TypeScript. * Use TypeScript interfaces and types extensively to define data structures and component props. * Enable strict mode in "tsconfig.json". **Don't Do This:** * Using "any" type excessively, defeating the purpose of TypeScript. * Ignoring TypeScript compiler errors. **Why:** TypeScript reduces runtime errors, improves code readability, and facilitates code refactoring, especially in large and complex projects. **Example (TypeScript component):** """typescript import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; interface Props { name: string; age: number; } const Greeting: React.FC<Props> = ({ name, age }) => { return ( <View style={styles.container}> <Text>Hello, {name}!</Text> <Text>You are {age} years old.</Text> </View> ); }; const styles = StyleSheet.create({ container: { padding: 10, backgroundColor: '#eee', margin: 5, }, }); export default Greeting; """ ### 1.5. Debugging **Standard:** Leverage React Native Debugger or Chrome DevTools for debugging JavaScript code. Use Flipper for inspecting native modules, logs, and network requests. **Do This:** * Install React Native Debugger standalone app. * Install Flipper desktop app. * Enable remote debugging in the React Native app. * Use "console.log", "console.warn", and "console.error" judiciously for debugging. **Don't Do This:** * Relying solely on "alert" for debugging. * Leaving debugging statements in production code. **Why:** React Native Debugger and Flipper provide powerful tools for inspecting the application state, network requests, and native module behavior, making debugging more efficient. **Example (Using Flipper):** 1. Install the required Flipper plugins (React, Network, Layout). 2. Ensure the React Native app is connected to Flipper. 3. Inspect the component hierarchy, network requests, and logs in the Flipper UI. ### 1.6. EAS Build and Update **Standard:** Employ EAS (Expo Application Services) Build for building and distributing your application. Use EAS Update for managing over-the-air updates. **Do This:** * Configure your "eas.json" to define build profiles. * Utilize EAS CLI build command for creating builds: "eas build -p android --profile production" * Leverage EAS update push commands when deploying updates **Don't Do This:** * Relying on manual build processes which are error-prone. * Neglecting over-the-air updates when applicable. **Why:** EAS simplifies the complex process of building and distributing React Native apps to the app stores, saving time and reducing configuration overhead. EAS Update enables faster iteration cycles by deploying code changes directly to users without requiring a new app store submission. ## 2. UI Libraries and Component Design ### 2.1. Component Libraries **Standard:** Choose a UI component library like React Native Paper, NativeBase, or UI Kitten based on the specific project's needs and design requirements. **Do This:** * Evaluate different component libraries based on their features, performance, and customization options. * Use a consistent component library throughout the project for a unified look and feel. * Customize the component library's theme to match the application's branding. **Don't Do This:** * Mixing components from multiple UI libraries without a clear reason, leading to visual inconsistencies and increased bundle size. * Reinventing the wheel by creating custom components for common UI elements already provided by UI libraries. **Why:** UI component libraries provide pre-built, well-tested, and customizable UI elements that accelerate development and ensure a consistent user interface. **Example (React Native Paper):** """javascript import React from 'react'; import { Appbar, Button } from 'react-native-paper'; const MyComponent = () => { return ( <> <Appbar.Header> <Appbar.Content title="My App" /> </Appbar.Header> <Button mode="contained" onPress={() => console.log('Pressed')}> Press me </Button> </> ); }; export default MyComponent; """ ### 2.2. Styling **Standard:** Use StyleSheet for basic inline styles and consider using Styled Components or Emotion for more complex styling scenarios, particularly when dealing with dynamic styles or theming. **Do This:** * Use "StyleSheet.create" for performance optimization. * Adopt a consistent naming convention for style properties. * Leverage platform-specific styles using "Platform.OS". * Use theme providers from UI libraries or create a custom theme context for consistent styling across the application. **Don't Do This:** * Using inline styles excessively, leading to code duplication and performance issues. * Hardcoding colors and sizes directly in the components. **Why:** StyleSheet provides a performant way to define styles, while Styled Components and Emotion offer advanced features like dynamic styling, theming, and CSS-in-JS capabilities. **Example (Styled Components):** """javascript import styled from 'styled-components/native'; import { Platform } from 'react-native'; const StyledView = styled.View" background-color: ${props => props.theme.backgroundColor}; padding: 10px; margin: 5px; border-radius: 5px; ${Platform.select({ ios: " shadow-color: #000; shadow-offset: 0px 2px; shadow-opacity: 0.25; shadow-radius: 3.84px; ", android: " elevation: 5; ", })} "; const StyledText = styled.Text" color: ${props => props.theme.textColor}; font-size: 16px; "; export { StyledView, StyledText }; """ ### 2.3 State Management **Standard:** Choose Zustand, Jotai, Recoil, or Redux Toolkit depending on the complexity of your application and your team's experience. For simpler applications, consider using React Context with "useReducer" for local state management. **Do This:** * Carefully evaluate different state management libraries based on their learning curve, performance, and features. * Use Redux Toolkit for projects that require a predictable and centralized state management solution with features like middleware and devtools. * Use Zustand, Jotai or Recoil for light-weight approaches better suited for local or component-specific state. * Structure your state using atomic principles in Jotai, or slices using Redux Toolkit. * Avoid over-engineering state management for simple applications. **Don't Do This:** * Using global state for component-specific data. * Mutating state directly. **Why:** State management libraries provide structured and predictable ways to manage application state, especially in complex applications with multiple components sharing data. **Example (Zustand):** """javascript import create from 'zustand'; interface BearState { bears: number; increase: (by: number) => void; } const useStore = create<BearState>((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), })); export default useStore; """ ### 2.4. Navigation **Standard:** Use React Navigation library for handling navigation. **Do This:** * Use the latest version of React Navigation. * Define navigation stacks and tabs using the "createNativeStackNavigator", "createBottomTabNavigator", or "createDrawerNavigator" functions. * Pass parameters between screens using the "navigation.navigate" method. * Use "useNavigation" hook to access the navigation object from within a component. * Centralize your navigation configuration within a "NavigationService" to ease refactoring, and ensure that all routes are defined in a single place. **Don't Do This:** * Relying on third-party navigation solutions that are not actively maintained. * Passing large amounts of data as navigation parameters. * Using string-based route names directly without typing them. **Why:** React Navigation provides a flexible and extensible navigation solution for React Native applications, supporting various navigation patterns and customization options. **Example (React Navigation):** """javascript import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; const Stack = createNativeStackNavigator(); const HomeScreen = ({ navigation }) => { return ( <Button title="Go to Details" onPress={() => navigation.navigate('Details', { itemId: 86 })} /> ); }; const DetailsScreen = ({ route }) => { const { itemId } = route.params; return ( <Text>Details Screen, item ID: {JSON.stringify(itemId)}</Text> ); }; const App = () => { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Navigator> </NavigationContainer> ); }; export default App; """ ## 3. Data Fetching and APIs ### 3.1. HTTP Client **Standard:** Use "axios" or the built-in "fetch" API for making HTTP requests. **Do This:** * Use "async/await" syntax for handling asynchronous operations. * Handle errors gracefully using "try/catch" blocks. * Implement request timeouts to prevent indefinite loading states. * Use a base URL for API endpoints to simplify code maintenance. * Consider adopting a library like "react-query" or "swr" for enhanced data fetching capabilities like caching, background updates, and optimistic updates. **Don't Do This:** * Using deprecated libraries for making HTTP requests. * Ignoring potential errors during API calls. * Hardcoding API keys or sensitive data directly in the code. **Why:** "axios" and "fetch" provide reliable and flexible ways to interact with APIs, while "react-query" and "swr" offer additional features for managing data fetching. **Example (Axios with TypeScript):** """typescript import axios from 'axios'; interface User { id: number; name: string; email: string; } const getUsers = async (): Promise<User[]> => { try { const response = await axios.get<User[]>('https://jsonplaceholder.typicode.com/users'); return response.data; } catch (error) { console.error(error); throw error; } }; export default getUsers; """ ### 3.2. Data Persistence **Standard:** Use AsyncStorage for storing small amounts of data locally. For larger or more complex datasets, consider using SQLite, Realm, or other suitable database solutions. **Do This:** * Store user authentication tokens securely using Keychain or similar secure storage mechanisms. * Use a consistent naming convention for storage keys. * Implement data encryption for sensitive information. * Consider using a library like "redux-persist" to automatically persist and rehydrate Redux state. **Don't Do This:** * Storing large amounts of data in AsyncStorage, leading to performance issues. * Storing sensitive information in plain text. * Blocking the main thread with synchronous storage operations. **Why:** AsyncStorage provides a simple key-value storage solution, while SQLite and Realm offer more robust database capabilities for managing larger datasets. Secure storage mechanisms like Keychain help protect sensitive information. **Example (AsyncStorage):** """javascript import AsyncStorage from '@react-native-async-storage/async-storage'; const storeData = async (key, value) => { try { const jsonValue = JSON.stringify(value) await AsyncStorage.setItem(key, jsonValue) } catch (e) { console.error("Error storing data", e) } } const getData = async (key) => { try { const jsonValue = await AsyncStorage.getItem(key) return jsonValue != null ? JSON.parse(jsonValue) : null; } catch(e) { console.error("Error getting data", e) return null; } } """ ## 4. Testing ### 4.1. Unit Testing **Standard:** Use Jest or Mocha with Enzyme or React Testing Library for writing unit tests. **Do This:** * Write unit tests for all components, functions, and modules. * Use mocking to isolate components and functions during testing. * Aim for high test coverage. * Run tests automatically on every commit using a CI/CD pipeline. **Don't Do This:** * Skipping unit tests for critical parts of the application. * Writing tests that are too tightly coupled to the implementation details. **Why:** Unit tests help to ensure that the code is working correctly and to prevent regressions when making changes. **Example (Jest with React Testing Library):** """javascript import React from 'react'; import { render, screen } from '@testing-library/react-native'; import Greeting from '../Greeting'; //Assuming the Greeting component from the TypeScript example describe('Greeting Component', () => { it('renders the greeting message', () => { render(<Greeting name="John" age={30} />); expect(screen.getByText('Hello, John!')).toBeDefined(); expect(screen.getByText('You are 30 years old.')).toBeDefined(); }); }); """ ### 4.2. End-to-End Testing **Standard:** Use Detox or Appium for writing end-to-end tests. **Do This:** * Write end-to-end tests to verify the functionality of the entire application. * Run end-to-end tests on real devices or emulators. * Use a CI/CD pipeline to automatically run end-to-end tests on every commit. **Don't Do This:** * Relying solely on manual testing. * Skipping end-to-end tests for critical user flows. **Why:** End-to-end tests help to ensure that the entire application is working correctly and to prevent regressions when making changes. ## 5. Code Quality and Linting ### 5.1. Linting and Formatting **Standard:** Use ESLint with Prettier for code linting and formatting. **Do This:** * Configure ESLint with React Native specific rules. * Configure Prettier with consistent formatting options. * Integrate ESLint and Prettier with your IDE for real-time feedback. * Use a pre-commit hook to automatically lint and format the code before committing. **Don't Do This:** * Ignoring ESLint and Prettier warnings and errors. * Using inconsistent code formatting. **Why:** ESLint and Prettier help to enforce consistent coding standards and to prevent common coding errors, improving code quality and maintainability. **Example (.eslintrc.js):** """javascript module.exports = { root: true, extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier', ], parser: '@typescript-eslint/parser', plugins: ['react', '@typescript-eslint', 'prettier'], rules: { 'prettier/prettier': 'error', 'react/prop-types': 'off', // TypeScript handles prop types '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-explicit-any': 'warn', }, settings: { react: { version: 'detect', }, }, }; """ ### 5.2. Static Analysis **Standard:** Consider using SonarQube or similar static analysis tools to identify potential code quality issues. **Do This:** * Integrate static analysis tools with your CI/CD pipeline. * Address code quality issues identified by static analysis tools. **Don't Do This:** * Ignoring code quality issues identified by static analysis tools. **Why:** Static analysis tools can help to identify potential code quality issues that may not be caught by linting or testing. ## 6. Libraries to consider * **react-native-svg**: For dealing with vector graphics. * **lottie-react-native:** For animations. * **react-native-reanimated:** For creating smooth, complex animations and interactions. * **react-native-gesture-handler:** For native gesture handling and performance. * **formik / react-hook-form:** Form management. * **yup / zod:** Schema definition and validation. * **i18next:** Internationalization and localization. ## 7. Security Considerations * **Data Encryption:** Always encrypt sensitive data before storing it locally using libraries like "react-native-keychain". Implement end-to-end encryption where possible. * **Input Validation:** Validate all user inputs carefully to prevent injection attacks and other vulnerabilities. Use libraries like "yup" or "zod" for schema validation. * **Secure API Communication:** Use HTTPS for all API requests to protect data in transit. * **Dependency Management:** Regularly audit your project dependencies for vulnerabilities using tools like "npm audit" or "yarn audit". Keep dependencies up-to-date. * **Permissions:** Request only the necessary permissions from users. Explain why each permission is needed. * **Code Obfuscation:** Consider using code obfuscation to make it more difficult for attackers to reverse engineer your application. * **Authentication and Authorization:** Implement robust authentication and authorization mechanisms to protect user data. Use industry-standard protocols like OAuth 2.0 where appropriate. This coding standards document will be updated regularly to reflect the latest best practices in React Native development. Adherence to these standards will help to ensure that the code is high quality, maintainable, and performant.
# Security Best Practices Standards for React Native This document outlines security best practices for React Native development. Following these standards will help protect your applications against common vulnerabilities, ensuring the confidentiality, integrity, and availability of user data. This document assumes you already have a basic familiarity with React Native development. ## 1. Data Storage ### 1.1 Secure Local Storage **Why:** Storing sensitive information locally without proper encryption can expose it to unauthorized access. **Do This:** * Use secure storage libraries like "react-native-keychain" for storing sensitive data (passwords, tokens, API keys). This encrypts data at rest. * If "react-native-keychain" doesn't meet your needs for non-sensitive data, use "react-native-encrypted-storage", which offers a higher-level API than raw "AsyncStorage". **Don't Do This:** * Avoid storing sensitive data in plain text using "AsyncStorage". **Code Example:** """javascript import * as Keychain from 'react-native-keychain'; const storeCredentials = async (username, password) => { try { await Keychain.setGenericPassword(username, password); console.log('Credentials stored securely!'); } catch (error) { console.error('Keychain storage error:', error); } }; const retrieveCredentials = async () => { try { const credentials = await Keychain.getGenericPassword(); if (credentials) { console.log('Credentials retrieved successfully'); return { username: credentials.username, password: credentials.password, }; } else { console.log('No credentials stored'); return null; } } catch (error) { console.error('Keychain retrieval error:', error); return null; } }; // Usage Example const storeUserCredentials = async () => { await storeCredentials('myUsername', 'mySecretPassword'); }; const getUserCredentials = async () => { const credentials = await retrieveCredentials(); if (credentials) { console.log('Username:', credentials.username); console.log('Password:', credentials.password); } }; """ **Anti-Pattern:** Directly storing API keys in source code or configuration files. This makes them easily accessible if the code is compromised. **Great Code:** Utilizing environment variables and build-time configuration to manage API keys, combined with encrypted storage at runtime. """javascript // .env file (never commit this to your repository) API_KEY=your_secret_api_key """ """javascript // app.json { "expo": { "name": "MyApp", "slug": "myapp", "version": "1.0.0", "extra": { "apiKey": process.env.API_KEY } } } """ """javascript import Constants from 'expo-constants'; const getApiKey = () => { if (Constants.expoConfig && Constants.expoConfig.extra) { return Constants.expoConfig.extra.apiKey; } return null; }; const apiKey = getApiKey(); """ ### 1.2 Data Masking **Why:** Prevents direct exposure of sensitive data in logs, analytics, or error reports. **Do This:** * Mask sensitive data before logging or transmitting it. Libraries such as "crypto-js" can provide robust hashing. * Avoid logging sensitive information directly (e.g., passwords, credit card numbers). **Don't Do This:** * Log full user details or personally identifiable information (PII) without proper anonymization or redaction. **Code Example:** """javascript import CryptoJS from 'crypto-js'; const logEvent = (eventData) => { const maskedEventData = { ...eventData, creditCardNumber: eventData.creditCardNumber ? CryptoJS.SHA256(eventData.creditCardNumber).toString() : null, }; console.log('Event Data:', maskedEventData); // Send maskedEventData to analytics service }; // Usage Example const event = { type: 'payment', amount: 100, creditCardNumber: '1234567890123456', }; logEvent(event); """ ## 2. Network Communication ### 2.1 HTTPS Enforcement **Why:** Ensures data transmission is encrypted and prevents man-in-the-middle attacks. **Do This:** * Use HTTPS for all API requests. Verify SSL certificates to prevent SSL pinning bypasses. * Ensure your server-side infrastructure is configured to enforce HTTPS. **Don't Do This:** * Make API requests over HTTP, especially for sensitive data. **Code Example:** """javascript const fetchData = async () => { try { const response = await fetch('https://api.example.com/data', { // Note the HTTPS method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data = await response.json(); console.log('Data:', data); } catch (error) { console.error('Fetch error:', error); } }; """ **Anti-pattern:** Hardcoding "http" in your API URL. **Great Code:** Using environment variables to define the API base URL which has "https". """javascript const API_BASE_URL = process.env.API_BASE_URL; // Should be an https URL const fetchData = async () => { try { const response = await fetch("${API_BASE_URL}/data", { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data = await response.json(); console.log('Data:', data); } catch (error) { console.error('Fetch error:', error); } }; """ ### 2.2 SSL Pinning **Why:** Validates the server's SSL certificate to prevent man-in-the-middle attacks by only trusting a specific certificate or public key. **Do This:** * Implement SSL pinning using libraries like "react-native-ssl-pinning". **Don't Do This:** * Rely solely on the operating system's trusted certificate authorities; they can be compromised. **Code Example:** """javascript import { check } from 'react-native-ssl-pinning'; const fetchDataWithSSLPinning = async () => { try { const response = await check('api.example.com', { // replace with your API endpoint publicKey: 'YOUR_PUBLIC_KEY_HERE', // replace with your public key }); if (response.valid) { console.log('SSL Pinning Validation Successful!'); // Proceed with fetching data } else { console.error('SSL Pinning Validation Failed!'); // Handle the error (e.g., refuse connection) } } catch (error) { console.error('SSL Pinning Check Error:', error); // Handle the error } }; """ ### 2.3 Input Validation **Why:** Prevents injection attacks and ensures data integrity. **Do This:** * Validate all user inputs on both the client and server sides. Sanitize inputs to prevent injection attacks. * Use appropriate data types and formats for input fields. * Utilize validation libraries like "Yup" or "React Hook Form" with validation schemas. **Don't Do This:** * Trust client-side validation alone. Always validate on the server. * Directly use user input in database queries or commands without sanitization. **Code Example:** """javascript import * as Yup from 'yup'; import { useForm, Controller } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { TextInput, Button, View, Text } from 'react-native'; const validationSchema = Yup.object().shape({ email: Yup.string().email('Invalid email').required('Email is required'), password: Yup.string() .min(8, 'Password must be at least 8 characters') .required('Password is required'), }); const LoginForm = () => { const { control, handleSubmit, formState: { errors } } = useForm({ resolver: yupResolver(validationSchema) }); const onSubmit = data => { console.log(data); // Submit validated data }; return ( <View> <Controller control={control} render={({ field: { onChange, onBlur, value } }) => ( <TextInput style={{ borderWidth: 1, padding: 10, marginBottom: 10 }} onBlur={onBlur} onChangeText={onChange} value={value} placeholder="Email" keyboardType="email-address" /> )} name="email" defaultValue="" /> {errors.email && <Text style={{ color: 'red' }}>{errors.email.message}</Text>} <Controller control={control} render={({ field: { onChange, onBlur, value } }) => ( <TextInput style={{ borderWidth: 1, padding: 10, marginBottom: 10 }} onBlur={onBlur} onChangeText={onChange} value={value} placeholder="Password" secureTextEntry={true} /> )} name="password" defaultValue="" /> {errors.password && <Text style={{ color: 'red' }}>{errors.password.message}</Text>} <Button title="Login" onPress={handleSubmit(onSubmit)} /> </View> ); }; export default LoginForm; """ ## 3. Authentication and Authorization ### 3.1 Secure Authentication Flows **Why:** Robust authentication is the foundation of a secure application. **Do This:** * Implement multi-factor authentication (MFA) where possible. * Use established authentication protocols like OAuth 2.0 or OpenID Connect. * Store passwords securely using bcrypt or Argon2 hashing algorithms on the server-side. * Use biometric authentication (Face ID, Touch ID) via "expo-local-authentication" for enhanced security. **Don't Do This:** * Store passwords in plain text or use weak hashing algorithms like MD5 or SHA1. * Implement custom authentication protocols without proper security review. **Code Example (Biometric Authentication):** """javascript import * as LocalAuthentication from 'expo-local-authentication'; import { useState, useEffect } from 'react'; import { View, Text, Button, Alert } from 'react-native'; const BiometricAuth = () => { const [isBiometricSupported, setIsBiometricSupported] = useState(false); useEffect(() => { (async () => { const compatible = await LocalAuthentication.hasHardwareAsync(); setIsBiometricSupported(compatible); })(); }); const fallBackToDefaultAuth = () => { Alert.alert( 'Fallback Authentication', 'Please enter your password:', [ {text: 'Cancel', style: 'cancel'}, {text: 'OK', onPress: () => console.log('Password authentication handled here')}, ], {cancelable: false} ); }; const authenticate = async () => { const result = await LocalAuthentication.authenticateAsync({ promptMessage: 'Authenticate to access app', fallbackLabel: 'Enter Password', cancelLabel: 'Cancel', }); if(result.success) { Alert.alert('Authentication successful!'); } else { fallBackToDefaultAuth() console.log('Authentication failed', result.error); } }; return ( <View> <Text> {isBiometricSupported ? 'Your device is compatible with biometric authentication' : 'Face or Fingerprint scanner not available on this device'} </Text> <Button title="Authenticate" onPress={authenticate} /> </View> ); }; export default BiometricAuth; """ ### 3.2 Authorization **Why:** Prevents unauthorized access to resources. **Do This:** * Implement role-based access control (RBAC) to restrict access based on user roles. * Use JSON Web Tokens (JWT) for secure authorization. Verify JWT signatures on the server-side. * Implement proper access control checks in your API endpoints. **Don't Do This:** * Rely solely on client-side authorization checks. * Store sensitive data in JWTs; use them only for authorization. **Code Example (JWT Verification - Example Node.js Server):** """javascript // Node.js server example const jwt = require('jsonwebtoken'); const verifyToken = (req, res, next) => { const token = req.headers['authorization']; if (!token) { return res.status(403).send({ message: 'No token provided!' }); } jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { return res.status(401).send({ message: 'Unauthorized!' }); } req.userId = decoded.id; next(); }); }; //Example usage (protecting an API endpoint) app.get('/api/profile',verifyToken, (req, res) => { //accessing data after succesfull authentication res.status(200).send({message: "Access Granted"}) }) """" ## 4. Dependencies and Updates ### 4.1 Dependency Management **Why:** Vulnerabilities in third-party libraries can compromise your application. **Do This:** * Regularly update dependencies to patch security vulnerabilities. * Use a dependency scanning tool to identify known vulnerabilities (e.g., "npm audit", "yarn audit"). * Lock dependencies using "package-lock.json" or "yarn.lock" to ensure consistent versions across environments. **Don't Do This:** * Use outdated or unmaintained libraries. * Ignore dependency security warnings. **Code Example (Dependency Update Workflow):** 1. Run "npm audit" or "yarn audit" 2. Review identified vulnerabilities. 3. Run "npm update" or "yarn upgrade" to update vulnerable packages. 4. Test your application thoroughly after updating dependencies. ### 4.2 React Native Upgrades **Why:** Newer versions of React Native often include important security patches and performance improvements. Failing to upgrade leaves your application vulnerable to known exploits and missing out on optimizations. **Do This:** * Keep React Native and related packages updated to at least the stable *minor* version releases. Major version upgrades should be evaluated on a seperate time frame. * Review the release notes for each update to identify security-related fixes and breaking changes. * Use "react-native upgrade" to update your React Native project. * Test your application thoroughly after each upgrade. * Pay close attention to the deprecation warnings, and address them according to the most up-to-date documentation **Don't Do This:** * Stay on outdated React Native versions. * Ignore the deprecation warnings. ## 5. Code Obfuscation (Defense in Depth) ### **5.1 Code Obfuscation** **Why:** Makes it harder for attackers to reverse engineer your app, understand its logic, and identify vulnerabilities **Do This:** * Use tools like ProGuard (Android) and JavaScript obfuscators (e.g., "javascript-obfuscator") as part of your build process * Consider commercial React Native build services to automate the obfuscation process **Don't Do This:** * Realize that it's not a silver bullet, but a deterrent. * Rely *solely* on obfuscation as your only security measure **Code Example (JavaScript Obfuscation using "javascript-obfuscator"):** 1. Install "javascript-obfuscator": """bash npm install javascript-obfuscator --save-dev """ 2. Create an obfuscation script (e.g., "obfuscate.js"): """javascript const JavaScriptObfuscator = require('javascript-obfuscator'); const fs = require('fs'); const fileToObfuscate = 'src/App.js'; // Replace with your main file const outputFile = 'dist/App.obfuscated.js'; fs.readFile(fileToObfuscate, 'utf8', (err, data) => { if (err) { console.error('Error reading file:', err); return; } const obfuscationResult = JavaScriptObfuscator.obfuscate(data, { compact: true, controlFlowFlattening: true, deadCodeInjection: true, // ... other options }); fs.writeFile(outputFile, obfuscationResult.getObfuscatedCode(), (err) => { if (err) { console.error('Error writing file:', err); } else { console.log('File obfuscated successfully!'); } }); }); """ 3. Add a script to your "package.json": """json "scripts": { "obfuscate": "node obfuscate.js" } """ 4. Run the obfuscation script: """bash npm run obfuscate """ 5. Update your build process to use the obfuscated file. ## 6. Sensitive Information Handling ### 6.1 In-Memory Handling **Why:** Minimizing the time sensitive data exists in memory reduces the attack surface **Do This:** * Clear sensitive data from memory as soon as it's no longer needed. * Avoid storing sensitive information in global variables. * Use local variables with limited scope for processing sensitive data. **Don't Do This:** * Leave sensitive data lingering in memory longer than necessary. * Rely on garbage collection to automatically clear sensitive data (it's not guaranteed). **Code Example:** """javascript const processSensitiveData = (data) => { let sensitiveInfo = data.creditCardNumber; //Do processing. sensitiveInfo = null; // Clear from memory console.warn("sensitiveInfo is NUll NOW.", sensitiveInfo) }; """ ## 7. Error Handling and Logging ### 7.1 Secure Error Handling **Why:** Prevents exposing sensitive information in error messages. **Do This:** * Implement custom error handling to prevent exposing sensitive data in stack traces or error messages. * Log errors to a secure location, not to the console in production. **Don't Do This:** * Display detailed error messages to end-users in production. * Log sensitive data in error logs. **Code Example:** """javascript const fetchData = async () => { try { const response = await fetch('https://api.example.com/data'); // Replace with your actual endpoint if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data = await response.json(); console.log('Data:', data); } catch (error) { // Log error to a secure logging service (e.g., Sentry, Firebase Crashlytics) console.error('Fetch error occurred. Refer to secure logs for details.'); // Display a generic error message to the user // Alert.alert('Error', 'An unexpected error occurred. Please try again later.'); } }; """ ## 8. Platform-Specific Considerations ### 8.1 iOS Security **Why:** iOS has specific security features that should be utilized. **Do This:** * Utilize the iOS Keychain for secure storage of credentials. * Implement App Transport Security (ATS) to enforce HTTPS connections. * Protect against jailbreaking by detecting and responding appropriately. ### 8.2 Android Security **Why:** Understand Android-specific attack vectors and mitigation strategies. **Do This:** * Utilize Android Keystore for secure storage of encryption keys and credentials. * Protect against reverse engineering and tampering using ProGuard. * Implement runtime application self-protection (RASP) techniques. ## 9. Regular Security Audits **Why:**Proactively identify and address vulnerabilities. **Do This:** * Conduct regular code reviews and security audits. * Use static analysis tools to identify potential security flaws. * Perform penetration testing to simulate real-world attacks. * Keep abreast of the latest security threats and vulnerabilities. ## Conclusion Maintaining a secure React Native application is an ongoing process. By following these best practices, you can significantly reduce the risk of security vulnerabilities and protect your users' data. Regular reviews, updates, and vigilance are key to ensuring a secure and reliable application.
# Code Style and Conventions Standards for React Native This document outlines code style and conventions standards for React Native development. Adhering to these standards promotes code readability, maintainability, consistency, and collaboration within development teams. It emphasizes modern approaches and patterns based on the latest React Native features and best practices. These standards aim to provide specific, actionable guidance with the 'why' behind each convention. ## 1. General Formatting ### 1.1. Indentation and Whitespace * **Do This:** Use 2 spaces for indentation. Avoid tabs. * **Don't Do This:** Use tabs or more than 2 spaces for indentation. * **Why:** Consistent indentation enhances code readability, making it easier to follow the code's structure. """javascript // Do This function MyComponent() { return ( <View> <Text>Hello, World!</Text> </View> ); } // Don't Do This function MyComponent() { return ( <View> <Text>Hello, World!</Text> </View> ); } """ ### 1.2. Line Length * **Do This:** Limit lines to a maximum of 120 characters. * **Don't Do This:** Exceed the line length limit. * **Why:** Shorter lines improve readability, especially on smaller screens or when viewing code side-by-side. ### 1.3. Trailing Commas * **Do This:** Use trailing commas in multi-line object literals, arrays, and function parameters. * **Don't Do This:** Omit trailing commas in multi-line structures. * **Why:** Trailing commas simplify adding, removing, or reordering items and reduce Git diff noise. """javascript // Do This const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', }, // Trailing comma }); // Don't Do This const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center' } }); """ ### 1.4. Whitespace Usage * **Do This:** Use whitespace to separate logical blocks of code. Add a newline between functions and components. Use single spaces around operators. * **Don't Do This:** Clutter code with excessive or inconsistent whitespace. """javascript // Do This function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } function MyComponent() { const result = add(5, 3); return ( <View> <Text>{result}</Text> </View> ); } // Don't Do This function add(a,b){return a+b;}function subtract(a,b){return a-b;} function MyComponent(){const result=add(5,3);return(<View><Text>{result}</Text></View>);} """ ### 1.5. Consistent Quote Usage * **Do This:** Use single quotes ("'") for JSX attributes and JavaScript strings. * **Don't Do This:** Use double quotes (""") in JSX attributes or inconsistently mix single and double quotes. * **Why:** Consistency improves readability, and single quotes are generally preferred in React/React Native. """javascript // Do This <Text style={styles.text}>{'Hello, World!'}</Text> // Don't Do This <Text style={styles.text}>"Hello, World!"</Text> """ ## 2. Naming Conventions ### 2.1. Variables and Constants * **Do This:** Use "camelCase" for variable and function names. Use "PascalCase" for component names. Use uppercase with underscores ("UPPER_CASE_WITH_UNDERSCORES") for constants. * **Don't Do This:** Use inconsistent casing or vague, non-descriptive names. * **Why:** Clear naming conventions improve code understanding and maintainability. """javascript // Do This const initialValue = 0; function calculateTotal() { /* ... */ } function MyComponent() { /* ... */ } const API_URL = 'https://example.com/api'; // Don't Do This const initVal = 0; // Vague name function calc() { /* ... */ } // Vague name function mycomponent() { /* ... */ } // Incorrect casing const apiUrl = 'https://example.com/api'; // Incorrect casing for constant """ ### 2.2. Component File Naming * **Do This:** Name component files using "PascalCase" with ".js" or ".jsx" extensions (e.g., "MyComponent.js"). Prefer ".jsx" for files containing JSX. * **Don't Do This:** Use inconsistent naming schemes or non-descriptive file names. * **Why:** Consistent file naming facilitates easier file searching and project organization. ### 2.3. Style Sheet Naming * **Do This:** Name style sheets using "PascalCase" matching the component name (e.g., "MyComponent.styles.js"). * **Don't Do This:** Use generic names like "styles.js" or "stylesheet.js", which can lead to confusion. Isolate styles in separate files for better organization. * **Why:** Clear style sheet naming clarifies the relationship between components and their associated styles. """javascript // MyComponent.js import React from 'react'; import { View, Text } from 'react-native'; import styles from './MyComponent.styles'; function MyComponent() { return ( <View style={styles.container}> <Text style={styles.text}>Hello, World!</Text> </View> ); } export default MyComponent; // MyComponent.styles.js import { StyleSheet } from 'react-native'; export default StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, text: { fontSize: 20, }, }); """ ### 2.4. Prop Naming * **Do This:** Use "camelCase" for props. Use descriptive names that clearly indicate the prop's purpose. * **Don't Do This:** Use short, ambiguous names or inconsistent casing. * **Why:** Meaningful prop names improve component API clarity. """javascript // Do This function MyComponent({ itemName, isEnabled }) { return ( <View> <Text>{itemName}</Text> {isEnabled ? <Text>Enabled</Text> : <Text>Disabled</Text>} </View> ); } // Don't Do This function MyComponent({ name, enabled }) { // Less descriptive names return ( <View> <Text>{name}</Text> {enabled ? <Text>Enabled</Text> : <Text>Disabled</Text>} </View> ); } """ ## 3. Component Structure and Best Practices ### 3.1. Functional Components with Hooks * **Do This:** Prefer functional components with Hooks over class components. * **Don't Do This:** Rely solely on class components, especially for new development. * **Why:** Functional components with Hooks offer a more concise and readable way to manage state and side effects. """javascript // Do This (Functional component with Hooks) import React, { useState } from 'react'; import { View, Text, Button } from 'react-native'; function Counter() { const [count, setCount] = useState(0); return ( <View> <Text>Count: {count}</Text> <Button title="Increment" onPress={() => setCount(count + 1)} /> </View> ); } export default Counter; // Don't Do This (Class component - less preferred) import React, { Component } from 'react'; import { View, Text, Button } from 'react-native'; class Counter extends Component { constructor(props) { super(props); this.state = { count: 0 }; } increment = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <View> <Text>Count: {this.state.count}</Text> <Button title="Increment" onPress={this.increment} /> </View> ); } } export default Counter; """ ### 3.2. Component Composition * **Do This:** Favor component composition over inheritance. Create small, reusable components. * **Don't Do This:** Build large, monolithic components or rely on deep inheritance hierarchies. * **Why:** Component composition promotes code reuse, testability, and maintainability. ### 3.3. Prop Types * **Do This:** Use TypeScript for prop type validation and static typing. * **Don't Do This:** Avoid defining prop types, or depend heavily on "PropTypes" runtime checking (use TypeScript instead). * **Why:** TypeScript provides compile-time type checking, leading to fewer runtime errors and better code maintainability, especially in large projects. """typescript // Do This (TypeScript) import React from 'react'; import { View, Text } from 'react-native'; interface Props { name: string; age?: number; // Optional prop } const MyComponent: React.FC<Props> = ({ name, age }) => { return ( <View> <Text>Name: {name}</Text> {age && <Text>Age: {age}</Text>} </View> ); }; export default MyComponent; """ ### 3.4. Stateless Components * **Do This:** Use stateless (pure) functional components whenever possible. * **Don't Do This:** Introduce state unnecessarily. * **Why:** Stateless components are simpler, easier to test, and improve performance. It avoids unnecessary re-renders if props haven’t changed. ### 3.5. Controlled vs. Uncontrolled Components * **Do This:** USe controlled components when you need to strictly control the input values. Use uncontrolled components if you just need to access the value when the form is submitted. """jsx // Controlled Component import React, { useState } from 'react'; import { View, TextInput } from 'react-native'; function ControlledInput() { const [text, setText] = useState(''); return ( <View> <TextInput value={text} onChangeText={setText} placeholder="Enter text" /> </View> ); } export default ControlledInput; // Uncontrolled Component import React, { useRef } from 'react'; import { View, TextInput, Button } from 'react-native'; function UncontrolledInput() { const inputRef = useRef(null); const handleSubmit = () => { console.log('Input value:', inputRef.current.value); // Or .current.props.value depending on RN version }; return ( <View> <TextInput ref={inputRef} defaultValue="" placeholder="Enter text" /> <Button title="Submit" onPress={handleSubmit} /> </View> ); } export default UncontrolledInput; """ ## 4. Styling ### 4.1. StyleSheet API * **Do This:** Use the "StyleSheet" API for defining styles. * **Don't Do This:** Use inline styles excessively. * **Why:** "StyleSheet" optimizes style creation and usage, enhancing performance. It also allows for better style management. """javascript // Do This import { StyleSheet } from 'react-native'; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, text: { fontSize: 20, }, }); // Don't Do This (Excessive inline styles) <View style={{ flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center' }}> <Text style={{ fontSize: 20 }}>Hello, World!</Text> </View> """ ### 4.2. Style Organization * **Do This:** Keep styles close to the component using them (e.g., "MyComponent.styles.js"). Employ separate files for global styles. * **Don't Do This:** Dump all styles into a single, massive stylesheet file. Adopt folder-based structure. * **Why:** Organized styles improve maintainability and reduce naming conflicts. ### 4.3. Platform-Specific Styles * **Do This:** Use "Platform.OS" to apply platform-specific styles. Employ the ".ios." and ".android." extensions for platform-specific files. * **Don't Do This:** Hardcode platform-specific values directly within components. * **Why:** Platform-specific styles ensure a consistent look and feel across different devices. """javascript // Do This import { StyleSheet, Platform } from 'react-native'; const styles = StyleSheet.create({ container: { paddingTop: Platform.OS === 'ios' ? 20 : 0, }, text: { fontSize: Platform.OS === 'ios' ? 16 : 14, }, }); // MyComponent.ios.js import React from 'react'; import { View, Text } from 'react-native'; import styles from './MyComponent.styles'; function MyComponent() { return ( <View style={styles.container}> <Text style={styles.text}>iOS Component</Text> </View> ); } export default MyComponent; // MyComponent.android.js import React from 'react'; import { View, Text } from 'react-native'; import styles from './MyComponent.styles'; function MyComponent() { return ( <View style={styles.container}> <Text style={styles.text}>Android Component</Text> </View> ); } export default MyComponent; """ ### 4.4. Themeing * **Do This:** Use a Context API or a dedicated library like React Navigation's theming support to implement themes. * **Don't Do This:** Hardcode colours and other styles across your application. * **Why:** Themeing allows for easily customisable styles across your entire application. """javascript // ThemeContext.js import React, { createContext, useState, useContext } from 'react'; const ThemeContext = createContext(); export const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState('light'); // Default theme const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }; export const useTheme = () => useContext(ThemeContext); // Styles.js import { StyleSheet } from 'react-native'; export const lightTheme = StyleSheet.create({ container: { backgroundColor: '#FFFFFF', color: '#000000', }, text: { color: '#000000', }, }); export const darkTheme = StyleSheet.create({ container: { backgroundColor: '#000000', color: '#FFFFFF', }, text: { color: '#FFFFFF', }, }); // Component import React from 'react'; import { View, Text, Button } from 'react-native'; import { useTheme } from './ThemeContext'; import { lightTheme, darkTheme } from './styles'; function ThemedComponent() { const { theme, toggleTheme } = useTheme(); const styles = theme === 'light' ? lightTheme : darkTheme; return ( <View style={styles.container}> <Text style={styles.text}>Themed Text</Text> <Button title="Toggle Theme" onPress={toggleTheme} /> </View> ); } export default ThemedComponent; // App.js usage import React from 'react'; import { ThemeProvider } from './ThemeContext'; import ThemedComponent from './ThemedComponent'; function App() { return ( <ThemeProvider> <ThemedComponent /> </ThemeProvider> ); } export default App; """ ## 5. State Management ### 5.1. Local State * **Do This:** Use "useState" for simple, component-specific state. * **Don't Do This:** Overuse global state management for local concerns. * **Why:** Reduces complexity and improves component isolation. ### 5.2. Global State * **Do This:** Consider using Context API with "useReducer" or dedicated libraries like Redux, Zustand, or Jotai for managing complex app-wide state. * **Don't Do This:** Mutate state directly. * **Why:** It centralizes state logic, improves data flow predictability, and facilitates state sharing across the application. """javascript // Do This (Context API with useReducer) import React, { createContext, useReducer, useContext } from 'react'; const initialState = { count: 0 }; const reducer = (state, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }; const CounterContext = createContext(); export const CounterProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <CounterContext.Provider value={{ state, dispatch }}> {children} </CounterContext.Provider> ); }; export const useCounter = () => useContext(CounterContext); // Component import React from 'react'; import { View, Text, Button } from 'react-native'; import { useCounter } from './CounterContext'; function CounterComponent() { const { state, dispatch } = useCounter(); return ( <View> <Text>Count: {state.count}</Text> <Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} /> <Button title="Decrement" onPress={() => dispatch({ type: 'DECREMENT' })} /> </View> ); } export default CounterComponent; // App.js usage import React from 'react'; import { CounterProvider } from './CounterContext'; import CounterComponent from './CounterComponent'; function App() { return ( <CounterProvider> <CounterComponent /> </CounterProvider> ); } export default App; """ ### 5.3. Immutable Data Structures * **Do This:** Use immutable data structures to avoid accidental mutations. * **Don't Do This:** Directly modify state objects. * **Why:**Immutability prevents unexpected side effects and makes debugging easier. """javascript // Do This const updateObject = (oldObject, newValues) => { return Object.assign({}, oldObject, newValues); }; // Don't Do This state.myProperty = newValue; // Avoid direct mutations """ ## 6. Asynchronous Operations ### 6.1. Async/Await * **Do This:** Use "async/await" for asynchronous operations (e.g., API calls). * **Don't Do This:** Rely heavily on ".then()" chains. * **Why:** "async/await" provides a more readable and manageable way to handle asynchronous code. """javascript // Do This async function fetchData() { try { const response = await fetch('https://example.com/api'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } } // Don't Do This (Callback chains) function fetchData() { fetch('https://example.com/api') .then(response => response.json()) .then(data => { console.log(data); }) .catch(error => { console.error('Error fetching data:', error); }); } """ ### 6.2. Error Handling * **Do This:** Implement proper error handling using "try/catch" blocks. Display user-friendly error messages. * **Don't Do This:** Ignore potential errors during asynchronous operations. * **Why:** Robust error handling prevents application crashes and improves the user experience. ## 7. Navigation ### 7.1. React Navigation * **Do This:** Use "react-navigation" for handling app navigation. Use version 6.x or later. * **Don't Do This:** Implement custom navigation solutions unless absolutely necessary. * **Why:** "react-navigation" is a well-maintained and feature-rich navigation library. ### 7.2. Navigation Patterns * **Do This:** Use appropriate navigation patterns (e.g., stack, tab, drawer) based on the application's structure. * **Don't Do This:** Inconsistently mix navigation patterns, which can confuse users. """javascript // Do This (React Navigation example) import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import HomeScreen from './HomeScreen'; import DetailsScreen from './DetailsScreen'; const Stack = createStackNavigator(); function App() { return ( <NavigationContainer> <Stack.Navigator initialRouteName="Home"> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Navigator> </NavigationContainer> ); } export default App; """ ## 8. Accessibility ### 8.1. Accessibility Properties * **Do This:** Use accessibility properties (e.g., "accessible", "accessibilityLabel", "accessibilityHint") to make the app accessible to users with disabilities. * **Don't Do This:** Ignore accessibility considerations. """javascript // Do This <Button title="Submit" accessible={true} accessibilityLabel="Submit the form" accessibilityHint="Submits the data entered in the form" onPress={handleSubmit} /> """ ### 8.2. Semantic HTML * **Do This:** Use semantic HTML-like components where appropriate (e.g., "Heading", "Paragraph") to provide structure and meaning to content. ## 9. Performance ### 9.1. Optimize Renderings * **Do This:** Use "React.memo" for functional components and "PureComponent" for class components to prevent unnecessary re-renders. Use the "useCallback" and "useMemo" hooks where applicable. * **Don't Do This:** Rely on default shouldComponentUpdate/React.memo comparisons, especially when dealing with complex data structures. * **Why:** Optimizing renderings improves app performance and reduces resource consumption. """javascript // Do This import React from 'react'; import { View, Text } from 'react-native'; const MyComponent = React.memo(({ data }) => { console.log('Rendering MyComponent'); return ( <View> <Text>{data.name}</Text> </View> ); }, (prevProps, nextProps) => { return prevProps.data.id === nextProps.data.id; // Custom comparison function }); export default MyComponent; import React, {useCallback} from 'react'; import { Button } from 'react-native'; function ParentComponent({onPress}) { const handleClick = useCallback(() => { onPress(); }, [onPress]); return ( <Button title="Click me" onPress={handleClick} /> ); } export default ParentComponent; """ ### 9.2. Image Optimization * **Do This:** Optimize images for mobile devices. Use appropriate image formats (e.g., WebP). Lazy-load images where possible. * **Don't Do This:** Use large, unoptimized images that slow down the app. ### 9.3. List Performance * **Do This:** Use "FlatList" or "SectionList" for rendering large lists. Implement "keyExtractor" and "getItemLayout" for improved performance. * **Don't Do This:** Use "ScrollView" for rendering long lists, as it can lead to performance issues. """javascript // Do This import React from 'react'; import { FlatList, Text, View } from 'react-native'; const data = Array.from({ length: 100 }, (_, i) => ({ id: i.toString(), name: "Item ${i}" })); function MyList() { const renderItem = ({ item }) => ( <View> <Text>{item.name}</Text> </View> ); const keyExtractor = (item) => item.id; return ( <FlatList data={data} renderItem={renderItem} keyExtractor={keyExtractor} /> ); } """ ### 9.4 Networking Optimizations * **Do This:** Use "useMemo" to memoize data that is expensive to compute. * **Don't Do This:** Create inline functions which will be re-created on every render. * **Why:** Creating inline functions will cause unnecessary re-renders. ## 10. Security ### 10.1. Data Validation * **Do This:** Validate user input on both the client and server sides. Use secure storage for sensitive data (e.g., "react-native-keychain"). * **Don't Do This:** Trust user input without validation. Store sensitive data in plain text. * **Why:** Proper data validation prevents injection attacks and data corruption. ### 10.2. Secure API Communication * **Do This:** Use HTTPS for all API communication. Implement authentication and authorization mechanisms. * **Don't Do This:** Use insecure HTTP connections for sensitive data. ### 10.3. Dependency Management * **Do This:** Regularly update dependencies to patch security vulnerabilities. Audit dependecies with "yarn audit" or "npm audit". * **Don't Do This:** Use outdated dependencies with known security flaws. ## 11. Testing ### 11.1. Unit Tests * **Do This:** Write unit tests for individual components and functions using Jest and React Native Testing Library. * **Don't Do This:** Neglect unit testing, leading to undetected bugs. ### 11.2. Integration Tests * **Do This:** Implement integration tests to verify interactions between different parts of the application. * **Don't Do This:** Skip integration testing, as it's crucial for validating app workflows. ### 11.3. End-to-End Tests * **Do This:** Use end-to-end testing frameworks (e.g., Detox, Appium) to test the complete user experience. * **Don't Do This:** Avoid end-to-end testing, which can lead to issues in production. ## 12. Documentation ### 12.1. Code Comments * **Do This:** Write clear and concise comments to explain complex logic or non-obvious code sections. Use JSDoc for documenting functions and components. * **Don't Do This:** Over-comment or write redundant comments that state the obvious. """javascript /** * Calculates the total price of items in the cart. * @param {Array<Object>} cartItems - An array of cart items. * @returns {number} The total price. */ function calculateTotalPrice(cartItems) { // Implementation... } """ ### 12.2. README Files * **Do This:** Provide comprehensive README files for projects and modules, including setup instructions, usage examples, and API documentation. * **Don't Do This:** Neglect documentation, making it difficult for others to understand and use the code. ## 13. Tooling ### 13.1. ESLint * **Do This:** Use ESLint with a consistent configuration (e.g., Airbnb, Standard) to enforce code style and detect potential issues. * **Don't Do This:** Ignore ESLint warnings and errors. ### 13.2. Prettier * **Do This:** Use Prettier to automatically format code and maintain consistency. Integrate Prettier with ESLint. * **Don't Do This:** Manually format code, leading to inconsistencies. ### 13.3. TypeScript * **Do This:** Use TypeScript (as implemented in section 3.3) for static typing. * **Don't Do This:** Use JavaScript without type checking, which makes it harder to catch bugs early. ## 14. Git Workflow ### 14.1. Branching Strategy * **Do This:** Use a clear branching strategy (e.g., Gitflow, GitHub Flow) to manage code changes and releases. * **Don't Do This:** Commit directly to the "main"/"master" branch or use inconsistent branching practices. ### 14.2. Commit Messages * **Do This:** Write descriptive commit messages that explain the purpose of each change. Follow conventional commits. * **Don't Do This:** Write vague or meaningless commit messages. * **Why:** Clear commit messages aid in code review, debugging, and team collaboration. ### 14.3. Code Reviews * **Do This:** Conduct thorough code reviews before merging branches. Use pull requests for code review. * **Don't Do This:** Skip code reviews, as they're essential for maintaining code quality. By adhering to these code style and conventions standards, React Native development teams can ensure code quality, improve collaboration, and build maintainable, performant, and secure applications. This document should be considered a living guide to be updated and refined as new technologies and best practices emerge.
# Testing Methodologies Standards for React Native This document outlines the testing methodologies standards for React Native applications. These guidelines ensure code quality, maintainability, and reliability by establishing best practices for unit, integration, and end-to-end testing within the React Native ecosystem. ## 1. General Testing Principles ### 1.1. Test-Driven Development (TDD) & Behavior-Driven Development (BDD) **Do This:** * Consider adopting TDD or BDD principles where appropriate. Write tests *before* implementing the code. This helps clarify requirements and design a more testable architecture from the outset. **Don't Do This:** * De-prioritize testing altogether or defer it until the end of a feature's development. This often results in poorly tested code and increased debugging effort. **Why:** * TDD/BDD can significantly improve code quality by driving design and ensuring features meet requirements early. It reduces defects and simplifies debugging. ### 1.2. Test Pyramid **Do This:** * Adhere to the test pyramid concept: * **Unit Tests (Top of Pyramid, Largest Quantity):** Focus on individual components and functions. * **Integration Tests (Middle of Pyramid, Moderate Quantity):** Verify interactions between multiple components. * **End-to-End (E2E) Tests (Bottom of Pyramid, Smallest Quantity):** Simulate real user flows across the entire application. **Don't Do This:** * Over-rely on E2E tests at the expense of unit and integration tests. E2E tests are slower and more brittle which leads to longer build times and decreased developer productivity. **Why:** * The test pyramid provides a balanced approach to testing, maximizing coverage while minimizing test execution time and maintenance effort. ### 1.3. Code Coverage **Do This:** * Aim for high code coverage (80%+), but focus on *meaningful* tests. Test all core logic, edge cases, and error conditions. **Don't Do This:** * Strive for 100% code coverage at all costs. Writing superficial tests solely to increase coverage isn't productive and can create maintenance overhead. * Ignore code coverage reports. Use them to identify untested areas and prioritize test development. **Why:** * Code coverage metrics help identify untested areas which potentially harbor bugs but should not be treated as the sole measure of test effectiveness. Prioritize writing tests that address critical functionality. ## 2. Unit Testing ### 2.1. Scope **Do This:** * Unit tests should focus on individual components, functions, or modules in isolation. * Mock dependencies (e.g., API calls, data stores, context providers, native modules) to isolate the unit under test. **Don't Do This:** * Allow side effects or external dependencies to influence unit tests. This makes tests unreliable and difficult to debug. **Why:** * Isolating the unit under test ensures that failures are localized, and tests run quickly. This promotes maintainability and rapid feedback. ### 2.2. Tools & Libraries **Do This:** * Use Jest as the primary testing framework. It is the recommended and most widely supported framework for React Native. * Use React Native Testing Library to test React components in a user-centric way. **Don't Do This:** * Use shallow rendering excessively. Prioritize testing the user-visible aspects of your components using React Native Testing Library or similar. **Why:** * Jest provides a comprehensive testing environment with mocking, assertions, and snapshot testing capabilities. React Native Testing Library encourages testing components from the user's perspective. ### 2.3. Example: Unit Testing a Simple Component """javascript // src/components/Greeting.js import React from 'react'; import { Text, View } from 'react-native'; const Greeting = ({ name }) => { return ( <View> <Text>Hello, {name}!</Text> </View> ); }; export default Greeting; """ """javascript // src/components/Greeting.test.js import React from 'react'; import { render, screen } from '@testing-library/react-native'; import Greeting from './Greeting'; describe('Greeting Component', () => { it('renders a greeting message with the provided name', () => { render(<Greeting name="John" />); const greetingElement = screen.getByText('Hello, John!'); expect(greetingElement).toBeDefined(); }); it('renders default greeting if no name is provided', () => { render(<Greeting />); const greetingElement = screen.getByText('Hello, undefined!'); //Avoid this: rely on proptypes for default values }); }); """ ### 2.4. Mocking **Do This:** * Use Jest's mocking capabilities to isolate the unit under test. Use "jest.mock()" to mock modules and "jest.fn()" to create mock functions. For mocking React Context: create a mock context provider. **Don't Do This:** * Over-mock. Only mock dependencies that prevent you from testing the core logic of the unit. * Mock implementation details. Mock only the public interface of dependencies. **Why:** * Mocking ensures that tests are deterministic and independent of external factors. It also allows you to simulate different scenarios, such as error states. """javascript // Example: Mocking an API call // src/api/userApi.js const fetchUserData = async (userId) => { const response = await fetch("https://api.example.com/users/${userId}"); const data = await response.json(); return data; }; export default fetchUserData; // src/components/UserProfile.js import React, { useState, useEffect } from 'react'; import { Text, View } from 'react-native'; import fetchUserData from '../api/userApi'; const UserProfile = ({ userId }) => { const [user, setUser] = useState(null); useEffect(() => { const loadUserData = async () => { const userData = await fetchUserData(userId); setUser(userData); }; loadUserData(); }, [userId]); if (!user) { return <Text>Loading...</Text>; } return ( <View> <Text>Name: {user.name}</Text> <Text>Email: {user.email}</Text> </View> ); }; export default UserProfile; // src/components/UserProfile.test.js import React from 'react'; import { render, screen, waitFor } from '@testing-library/react-native'; import UserProfile from './UserProfile'; import fetchUserData from '../api/userApi'; jest.mock('../api/userApi'); // Mock the entire module describe('UserProfile Component', () => { it('fetches and displays user data', async () => { const mockUserData = { id: 1, name: 'John Doe', email: 'john.doe@example.com' }; fetchUserData.mockResolvedValue(mockUserData); // Mock the return value render(<UserProfile userId={1} />); // Wait for the data to load and the component to update await waitFor(() => { expect(screen.getByText('Name: John Doe')).toBeDefined(); expect(screen.getByText('Email: john.doe@example.com')).toBeDefined(); }); }); it('displays loading state', () => { render(<UserProfile userId={1} />); expect(screen.getByText('Loading...')).toBeDefined(); }); it('handles error cases', async () => { fetchUserData.mockRejectedValue(new Error('Failed to fetch user data')); render(<UserProfile userId={1} />); await waitFor(() => { console.log(screen.debug()); // useful for debugging async render issues! //Note: error handling should be implemented in component, // in this test component should have error state and error message. //expect(screen.getByText('Error')).toBeDefined(); //This line is a placeholder! }); }); }); """ ### 2.5. Snapshot Testing **Do This:** * Use snapshot testing sparingly for components with complex or dynamic layouts. Snapshots are useful for detecting unintended UI changes. **Don't Do This:** * Rely solely on snapshot testing. Snapshots capture the UI's structure, but they don't verify functionality or behavior. * Commit snapshots without reviewing them. Ensure that any changes in the snapshot are intentional and correct. **Why:** * Snapshot tests quickly detect unexpected UI changes. However, they are less effective at verifying the component's logic. ## 3. Integration Testing ### 3.1. Scope **Do This:** * Integration tests should focus on verifying the interactions between two or more components or modules. * Test how your components function with Redux, Context, or other data management solutions **Don't Do This:** * Test entire user flows in integration tests. Reserve end-to-end (E2E) tests for that purpose. **Why:** * Integration tests ensure that different parts of the application work together correctly. ### 3.2. Tools & Libraries **Do This:** * Continue using Jest and React Native Testing Library. * Use mocking strategically to isolate parts of the integration. **Don't Do This:** * Avoid mocking unnecessarily. Aim to test as much of the real integration as possible. **Why:** * Minimal mocking reduces the risk of tests that are too tightly coupled to the implementation details. ### 3.3. Example: Integration Testing a List and Item Component """javascript // src/components/ListItem.js import React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; const ListItem = ({ item, onPress }) => { return ( <TouchableOpacity onPress={onPress}> <View> <Text>{item.name}</Text> </View> </TouchableOpacity> ); }; export default ListItem; // src/components/List.js import React from 'react'; import { FlatList } from 'react-native'; import ListItem from './ListItem'; const List = ({ items, onItemPress }) => { return ( <FlatList data={items} renderItem={({ item }) => ( <ListItem item={item} onPress={() => onItemPress(item)} /> )} keyExtractor={(item) => item.id.toString()} /> ); }; export default List; // src/components/List.test.js import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react-native'; import List from './List'; describe('List Component', () => { const items = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }, ]; const onItemPress = jest.fn(); it('renders a list of items', () => { render(<List items={items} onItemPress={onItemPress} />); expect(screen.getByText('Item 1')).toBeDefined(); expect(screen.getByText('Item 2')).toBeDefined(); }); it('calls onItemPress when an item is pressed', () => { render(<List items={items} onItemPress={onItemPress} />); const item1 = screen.getByText('Item 1'); fireEvent.press(item1); expect(onItemPress).toHaveBeenCalledWith(items[0]); }); }); """ ## 4. End-to-End (E2E) Testing ### 4.1. Scope **Do This:** * E2E tests should focus on verifying critical user flows through the entire application. * Simulate real user interactions as closely as possible. **Don't Do This:** * Test minor UI details. Focus on core functionality and user journeys. * Over-test the same flows repeatedly. Optimize for coverage of distinct scenarios. **Why:** * E2E tests provide the greatest confidence in the application's overall functionality. ### 4.2. Tools & Libraries **Do This:** * Consider using Detox or Appium for E2E testing. Detox is well-suited for React Native due to its grey-box testing approach that synchronizes with the app's state. **Don't Do This:** * Attempt to use unit testing frameworks for E2E testing. They are not designed for this purpose. **Why:** * Detox and Appium provide tools to automate UI testing and interact with native device features. ### 4.3. Example: E2E Test with Detox Detox requires specific setup and configuration as outlined in its documentation. The following is a simplified example. """javascript // e2e/firstTest.e2e.js describe('Example', () => { beforeAll(async () => { await device.launchApp(); }); beforeEach(async () => { await device.reloadReactNative(); }); it('should show hello screen after tap', async () => { await element(by.id('hello_button')).tap(); await expect(element(by.text('Hello!!!'))).toBeVisible(); }); it('should show world screen after tap', async () => { await element(by.id('world_button')).tap(); await expect(element(by.text('World!!!'))).toBeVisible(); }); }); """ ### 4.4. Test Data Management **Do This:** * Use a separate test database or API endpoint for E2E tests. This prevents tests from interfering with real user data. * Reset test data before each test suite. **Don't Do This:** * Use real user data in E2E tests. This is a security risk. **Why** * Ensures Tests are run in isolation. ### 4.5. Flakiness Reduction **Do This:** * Implement retry mechanisms for flaky tests. * Use explicit waits to ensure that UI elements are visible before interacting with them. * Write atomic E2E tests. **Don't Do This:** * Ignore flaky tests. They indicate underlying issues in the application or the test environment. **Why:** * Flaky tests undermine confidence in the test suite. By implementing retry mechanisms, explicit waits, and Atomic E2E tests reduces the likelihood of them. ## 5. Test Naming Conventions **Do This:** * Use descriptive and consistent test names. * Structure your test names to answer the following questions: What is being tested? Under what circumstances? What is the expected outcome? **Don't Do This:** * Use vague or ambiguous test names. **Why:** * Clear and concise test names make it easier to understand the purpose of each test and debug failures. """javascript // Example: // Good: it('fetches user data and displays it in the UI', () => { ... }); // Bad: it('test', () => { ... }); """ ## 6. Continuous Integration (CI) **Do This:** * Integrate your tests into your CI/CD pipeline to automatically run them on every commit or pull request. **Don't Do This:** * Skip running tests in CI. **Why:** * Continuous integration ensures that tests are executed regularly, providing early feedback on code changes. It helps to catch regression bugs before they reach production. ## 7. Accessibility Testing **Do This:** * Write accessibility tests to ensure that your application is usable by people with disabilities. * Use accessibility testing tools like React Native AccessibilityInfo or native accessibility APIs. **Don't Do This:** * Ignore accessibility. An accessible application is an inclusive application. **Why:** * Accessibility testing ensures that your application is inclusive and adheres to accessibility standards like WCAG. ## 8. Performance Testing ### 8.1. Scope **Do This:** * Measure rendering performance using tools like "PerfMonitor". * Perform stress testing for key components. **Don't Do This:** * Ignore performance testing until late in the development cycle. **Why:** * Improves application performance ### 8.2. Tools & Libraries **Do This:** * Use tools like Flipper for profiling. * Use Jest for performance testing. """javascript //Performance test example // src/components/List.performance.test.js import List from './List'; import React from 'react'; import { render } from '@testing-library/react-native'; describe('List Component - Performance', () => { it('renders 1000 items without performance degradation', () => { const items = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: "Item ${i}" })); const startTime = performance.now(); render(<List items={items} onItemPress={() => {}} />); const endTime = performance.now(); const renderTime = endTime - startTime; console.log("Rendering 1000 items took ${renderTime} ms"); expect(renderTime).toBeLessThan(500); // Adjust the threshold as needed //If it's consistently taking over 500ms to render, your app might feel sluggish }); }); """ ## 9. Security Testing ### 9.1. Scope **Do This:** * Implement security tests to catch common vulnerabilities, such as data injection attacks. * Use static analysis tools to identify potential vulnerabilities in your code. **Don't Do This:** * Assume that your application is secure by default. * Store sensitive information, such as API keys, directly in your code. **Why:** * Improves the security of your application. ## 10. Deprecated Features * Double check your version of React Native and ensure that you are not calling any deprecated features. By following the testing methodology standards in your React Native projects, you ensure higher code quality, improve maintainability, and deliver a more reliable and secure user experience. Adapt and extend these standards to match the specific needs of your projects and organization.
# API Integration Standards for React Native This document outlines the coding standards and best practices for API integration in React Native applications. It aims to provide a comprehensive guide for developers to write maintainable, efficient, and secure code when interacting with backend services and external APIs. It covers modern approaches, design patterns, and the latest React Native features to ensure code quality and consistency. ## 1. Architectural Considerations ### 1.1. Decoupling API Logic **Standard:** Separate API interaction concerns from UI components to promote reusability and testability. * **Do This:** Use a dedicated service or repository layer for API calls. * **Don't Do This:** Directly make API calls within React components. **Why:** Decoupling makes components easier to test and reuse. It also simplifies debugging and allows for easier modifications to the API layer without affecting the UI. """javascript // Good: API service layer import axios from 'axios'; const API_BASE_URL = 'https://api.example.com'; const ApiService = { get: async (endpoint, params = {}) => { try { const response = await axios.get("${API_BASE_URL}/${endpoint}", { params }); return response.data; } catch (error) { console.error('API GET error:', error); throw error; // Re-throw the error for handling in the component } }, post: async (endpoint, data = {}) => { try { const response = await axios.post("${API_BASE_URL}/${endpoint}", data); return response.data; } catch (error) { console.error('API POST error:', error); throw error; // Re-throw the error for handling in the component } }, put: async (endpoint, data = {}) => { try { const response = await axios.put("${API_BASE_URL}/${endpoint}", data); return response.data; } catch (error) { console.error('API PUT error:', error); throw error; // Re-throw the error for handling in the component } }, delete: async (endpoint) => { try { const response = await axios.delete("${API_BASE_URL}/${endpoint}"); return response.data; } catch (error) { console.error('API DELETE error:', error); throw error; // Re-throw the error for handling in the component } } }; export default ApiService; // Example Usage in a component import React, { useState, useEffect } from 'react'; import { View, Text, ActivityIndicator } from 'react-native'; import ApiService from './ApiService'; const MyComponent = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const result = await ApiService.get('data'); setData(result); setLoading(false); } catch (err) { setError(err.message || 'An error occurred'); setLoading(false); } }; fetchData(); }, []); if (loading) { return <ActivityIndicator size="large" />; } if (error) { return <Text>Error: {error}</Text>; } return ( <View> <Text>Data: {data ? JSON.stringify(data) : 'No data'}</Text> </View> ); }; export default MyComponent; """ ### 1.2. Centralized Error Handling **Standard:** Implement a centralized error handling mechanism to manage API errors consistently. * **Do This:** Create custom error classes and a global error handler. * **Don't Do This:** Handle errors directly within individual API calls in components without a unified approach. **Why:** Centralized error handling provides a consistent user experience and simplifies debugging. """javascript // Custom error class class ApiError extends Error { constructor(message, statusCode) { super(message); this.name = 'ApiError'; this.statusCode = statusCode; } } // API service with error handling const ApiService = { get: async (endpoint, params = {}) => { try { const response = await axios.get("${API_BASE_URL}/${endpoint}", { params }); if (response.status >= 400) { throw new ApiError("Request failed with status ${response.status}", response.status); } return response.data; } catch (error) { console.error('API GET error:', error); if (error instanceof ApiError) { // Handle known API errors throw error; } else { // Wrap unexpected errors throw new ApiError('An unexpected error occurred', 500); } } } }; // Global error handler (example) const handleApiError = (error) => { if (error instanceof ApiError) { // Log the error, display a user-friendly message, or take other actions console.error('API Error:', error.message, error.statusCode); // Example: Display an alert to the user Alert.alert('API Error', error.message); } else { // Handle other types of errors (e.g., network errors) console.error('Unexpected Error:', error); Alert.alert('Error', 'An unexpected error occurred. Please try again later.'); } }; // Usage in a component import React, { useState, useEffect } from 'react'; import { View, Text, ActivityIndicator, Alert } from 'react-native'; import ApiService from './ApiService'; const MyComponent = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const result = await ApiService.get('data'); setData(result); setLoading(false); } catch (err) { setError(err); handleApiError(err); // Use the global error handler setLoading(false); } }; fetchData(); }, []); if (loading) { return <ActivityIndicator size="large" />; } if (error) { return <Text>Error: {error.message || 'An error occurred'}</Text>; } return ( <View> <Text>Data: {data ? JSON.stringify(data) : 'No data'}</Text> </View> ); }; export default MyComponent; """ ### 1.3. Data Transformation **Standard:** Transform API responses into a format suitable for the application's UI and data models. * **Do This:** Use a transformation function or layer between the API service and the UI. * **Don't Do This:** Directly use API responses in UI without validation or transformation. **Why:** Data transformation isolates the UI from API changes and ensures data consistency across the application. """javascript // API service const ApiService = { getUsers: async () => { const response = await axios.get("${API_BASE_URL}/users"); return response.data; } }; // Data transformation function const transformUser = (user) => ({ id: user.id, fullName: "${user.firstName} ${user.lastName}", email: user.email, }); // Usage in component import React, { useState, useEffect } from 'react'; import { View, Text } from 'react-native'; import ApiService from './ApiService'; const UserList = () => { const [users, setUsers] = useState([]); useEffect(() => { const fetchUsers = async () => { const apiUsers = await ApiService.getUsers(); const transformedUsers = apiUsers.map(transformUser); setUsers(transformedUsers); }; fetchUsers(); }, []); return ( <View> {users.map((user) => ( <Text key={user.id}>{user.fullName} - {user.email}</Text> ))} </View> ); }; export default UserList; """ ## 2. HTTP Client Selection and Configuration ### 2.1. Choosing the Right HTTP Client **Standard:** Use "axios" or the built-in "fetch" API for making HTTP requests. * **Do This:** Evaluate "axios" and "fetch" based on project requirements (e.g., interceptors, cancellation). "axios" is generally preferred for its ease of use and features, but "fetch" is a lightweight alternative if those are not needed. * **Don't Do This:** Rely on deprecated or less-maintained HTTP clients. **Why:** Modern HTTP clients offer better features, performance, and security compared to older libraries. "axios" provides automatic JSON transformation, request cancellation, and interceptors. ### 2.2. Configuring Timeout and Retries **Standard:** Set appropriate timeouts and implement retry mechanisms to handle network issues. * **Do This:** Configure timeouts to prevent indefinite waiting and use retry strategies for transient errors. * **Don't Do This:** Use default timeout settings or ignore network errors. """javascript // Axios with timeout and retry import axios from 'axios'; const API_BASE_URL = 'https://api.example.com'; const axiosInstance = axios.create({ baseURL: API_BASE_URL, timeout: 5000, // 5 seconds timeout }); axiosInstance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.code === 'ECONNABORTED' && !originalRequest._retry) { originalRequest._retry = true; console.log('Retrying request...'); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second before retrying return axiosInstance(originalRequest); } return Promise.reject(error); } ); const ApiService = { get: async (endpoint, params = {}) => { try { const response = await axiosInstance.get(endpoint, { params }); return response.data; } catch (error) { console.error('API GET error:', error); throw error; // Re-throw the error for handling in the component } } }; export default ApiService; """ ### 2.3. Handling Request Cancellation **Standard:** Implement request cancellation when the component unmounts or the user initiates a new action. * **Do This:** Use "AbortController" with "fetch" or cancellation tokens with "axios". * **Don't Do This:** Leave requests running in the background, which can lead to memory leaks and unexpected behavior. """javascript // Fetch with AbortController import React, { useState, useEffect } from 'react'; import { View, Text } from 'react-native'; const MyComponent = () => { const [data, setData] = useState(null); useEffect(() => { const abortController = new AbortController(); const signal = abortController.signal; const fetchData = async () => { try { const response = await fetch('https://api.example.com/data', { signal }); const result = await response.json(); setData(result); } catch (error) { if (error.name === 'AbortError') { console.log('Fetch aborted'); } else { console.error('Fetch error:', error); } } }; fetchData(); return () => { abortController.abort(); // Cancel the request on unmount }; }, []); return ( <View> <Text>Data: {data ? JSON.stringify(data) : 'Loading...'}</Text> </View> ); }; export default MyComponent; """ ## 3. Authentication and Authorization ### 3.1. Securely Storing Authentication Tokens **Standard:** Store authentication tokens securely using "react-native-keychain" or "AsyncStorage". * **Do This:** Use Keychain for sensitive data and "AsyncStorage" for less sensitive data. Encrypt tokens before storing them. * **Don't Do This:** Store tokens in plain text in memory or local storage. **Why:** Secure storage prevents unauthorized access to authentication tokens. "react-native-keychain" provides native-level security for iOS and Android. """javascript // Secure storage using react-native-keychain import * as Keychain from 'react-native-keychain'; const storeCredentials = async (username, password) => { try { await Keychain.setGenericPassword(username, password); console.log('Credentials stored securely'); } catch (error) { console.error('Keychain storage error:', error); } }; const retrieveCredentials = async () => { try { const credentials = await Keychain.getGenericPassword(); if (credentials) { console.log('Credentials retrieved successfully'); return credentials; } else { console.log('No credentials stored'); return null; } } catch (error) { console.error('Keychain retrieval error:', error); return null; } }; const deleteCredentials = async () => { try { await Keychain.resetGenericPassword(); console.log('Credentials deleted successfully'); } catch (error) { console.error('Keychain deletion error:', error); } }; // Usage example (login) const login = async (username, password) => { try { const response = await ApiService.post('login', { username, password }); const { token } = response; await storeCredentials(username, token); // Store the token securely // Further actions after successful login } catch (error) { console.error('Login failed:', error); } }; // Usage example (retrieve token) const getToken = async () => { const credentials = await retrieveCredentials(); if (credentials) { return credentials.password; // Return the token } return null; }; """ ### 3.2. Implementing Refresh Tokens **Standard:** Use refresh tokens to obtain new access tokens without requiring the user to re-authenticate. * **Do This:** Implement a refresh token flow that handles token expiration gracefully. * **Don't Do This:** Rely solely on long-lived access tokens. **Why:** Refresh tokens enhance security and user experience by minimizing the need for frequent re-authentication. """javascript // Axios interceptor for token refresh axiosInstance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = await retrieveRefreshToken(); if (!refreshToken) { // Redirect to login if no refresh token is available return Promise.reject(error); } const refreshResponse = await axios.post('/refresh', { refreshToken }); const { newAccessToken, newRefreshToken } = refreshResponse.data; await storeCredentials('username', newAccessToken); await storeRefreshToken(newRefreshToken); axiosInstance.defaults.headers.common['Authorization'] = "Bearer ${newAccessToken}"; originalRequest.headers['Authorization'] = "Bearer ${newAccessToken}"; return axiosInstance(originalRequest); } catch (refreshError) { // Redirect to login if refresh fails return Promise.reject(refreshError); } } return Promise.reject(error); } ); const storeRefreshToken = async (refreshToken) => { try { await AsyncStorage.setItem('refreshToken', refreshToken); } catch (error) { console.error('Error storing refresh token:', error); } }; const retrieveRefreshToken = async () => { try { const refreshToken = await AsyncStorage.getItem('refreshToken'); return refreshToken; } catch (error) { console.error('Error retrieving refresh token:', error); return null; } }; """ ### 3.3. Securing API Keys **Standard:** Protect API keys from exposure by using environment variables and secure storage. * **Do This:** Store API keys in ".env" files during development and use native secrets management in production. * **Don't Do This:** Hardcode API keys directly in the source code. **Why:** API keys are sensitive and should be protected to prevent abuse and unauthorized access. """javascript // Using environment variables import { API_KEY } from '@env'; const ApiService = { getData: async () => { const response = await axios.get("${API_BASE_URL}/data?apiKey=${API_KEY}"); return response.data; } }; """ ## 4. Data Handling and State Management ### 4.1. Optimistic Updates **Standard:** Implement optimistic updates for UI responsiveness. * **Do This:** Update the UI immediately after a request is initiated and revert the update if the request fails. * **Don't Do This:** Wait for the server response before updating the UI. **Why:** Optimistic updates provide a smoother user experience by making the UI feel more responsive. """javascript import React, { useState } from 'react'; import { View, Text, Button } from 'react-native'; import ApiService from './ApiService'; const LikeButton = ({ postId, initialLikes }) => { const [likes, setLikes] = useState(initialLikes); const [isLiked, setIsLiked] = useState(false); const handleLike = async () => { const previousLikes = likes; const newLikes = isLiked ? likes - 1 : likes + 1; // Optimistic update setLikes(newLikes); setIsLiked(!isLiked); try { await ApiService.post("posts/${postId}/like", { liked: !isLiked }); } catch (error) { // Revert update on error setLikes(previousLikes); setIsLiked(isLiked); console.error('Failed to update likes:', error); } }; return ( <View> <Text>Likes: {likes}</Text> <Button title={isLiked ? 'Unlike' : 'Like'} onPress={handleLike} /> </View> ); }; export default LikeButton; """ ### 4.2. Pagination **Standard:** Implement pagination for displaying large datasets. * **Do This:** Use server-side pagination to retrieve data in chunks and implement infinite scrolling or load more buttons. * **Don't Do This:** Load entire datasets at once, which can cause performance issues. """javascript import React, { useState, useEffect } from 'react'; import { View, Text, FlatList, Button, ActivityIndicator } from 'react-native'; import ApiService from './ApiService'; const ItemList = () => { const [items, setItems] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const fetchItems = async () => { if (loading || !hasMore) return; setLoading(true); try { const response = await ApiService.get("items?page=${page}&limit=10"); if (response.length === 0) { setHasMore(false); } else { setItems((prevItems) => [...prevItems, ...response]); setPage((prevPage) => prevPage + 1); } } catch (error) { console.error('Failed to fetch items:', error); setHasMore(false); } finally { setLoading(false); } }; useEffect(() => { fetchItems(); }, []); const renderItem = ({ item }) => ( <View> <Text>{item.name}</Text> </View> ); const loadMore = () => { fetchItems(); }; const renderFooter = () => ( loading ? ( <ActivityIndicator size="small" /> ) : hasMore ? ( <Button title="Load More" onPress={loadMore} /> ) : null ); return ( <FlatList data={items} renderItem={renderItem} keyExtractor={(item) => item.id.toString()} ListFooterComponent={renderFooter} onEndReached={loadMore} onEndReachedThreshold={0.5} /> ); }; export default ItemList; """ ### 4.3. Using State Management Libraries **Standard:** Use state management libraries like Redux, Zustand, or Context API for complex state management. * **Do This:** Choose a state management solution based on project complexity and team familiarity. * **Don't Do This:** Use local component state for data that needs to be shared across multiple components. **Why:** State management libraries simplify data sharing and ensure data consistency across the application. """javascript // Redux example // actions.js export const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST'; export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'; export const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE'; export const fetchDataRequest = () => ({ type: FETCH_DATA_REQUEST }); export const fetchDataSuccess = (data) => ({ type: FETCH_DATA_SUCCESS, payload: data }); export const fetchDataFailure = (error) => ({ type: FETCH_DATA_FAILURE, payload: error }); export const fetchData = () => { return async (dispatch) => { dispatch(fetchDataRequest()); try { const response = await ApiService.get('data'); dispatch(fetchDataSuccess(response)); } catch (error) { dispatch(fetchDataFailure(error)); } }; }; // reducer.js const initialState = { data: null, loading: false, error: null, }; const dataReducer = (state = initialState, action) => { switch (action.type) { case FETCH_DATA_REQUEST: return { ...state, loading: true, error: null }; case FETCH_DATA_SUCCESS: return { ...state, loading: false, data: action.payload }; case FETCH_DATA_FAILURE: return { ...state, loading: false, error: action.payload }; default: return state; } }; export default dataReducer; // component.js import React, { useEffect } from 'react'; import { View, Text, ActivityIndicator } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import { fetchData } from './actions'; const MyComponent = () => { const dispatch = useDispatch(); const data = useSelector((state) => state.data.data); const loading = useSelector((state) => state.data.loading); const error = useSelector((state) => state.data.error); useEffect(() => { dispatch(fetchData()); }, [dispatch]); if (loading) { return <ActivityIndicator size="large" />; } if (error) { return <Text>Error: {error.message || 'An error occurred'}</Text>; } return ( <View> <Text>Data: {data ? JSON.stringify(data) : 'No data'}</Text> </View> ); }; export default MyComponent; """ ## 5. Performance Optimization ### 5.1. Caching API Responses **Standard:** Cache API responses to reduce network requests and improve performance. * **Do This:** Use in-memory caching or persistent storage (e.g., "AsyncStorage") to store API responses. Utilize libraries like "react-native-cache" or implement custom caching logic. * **Don't Do This:** Cache sensitive data without proper encryption or ignore cache invalidation. **Why:** Caching reduces latency and bandwidth usage, resulting in a faster and more responsive application. """javascript // Caching with AsyncStorage import AsyncStorage from '@react-native-async-storage/async-storage'; const CACHE_KEY = 'api_data'; const CACHE_EXPIRY = 60 * 60 * 1000; // 1 hour const ApiService = { getData: async () => { const cachedData = await AsyncStorage.getItem(CACHE_KEY); const cacheExpiry = await AsyncStorage.getItem("${CACHE_KEY}_expiry"); if (cachedData && cacheExpiry && Date.now() < parseInt(cacheExpiry)) { console.log('Using cached data'); return JSON.parse(cachedData); } const response = await axios.get("${API_BASE_URL}/data"); const data = response.data; await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(data)); await AsyncStorage.setItem("${CACHE_KEY}_expiry", Date.now() + CACHE_EXPIRY); return data; } }; """ ### 5.2. Using Memoization **Standard:** Use memoization techniques to prevent unnecessary API calls. * **Do This:** Use "React.memo" for functional components and "shouldComponentUpdate" for class components to memoize API results based on props. * **Don't Do This:** Make redundant API calls when the data hasn't changed. **Why:** Memoization optimizes rendering performance by preventing re-renders when the input props haven't changed. """javascript import React from 'react'; import { View, Text } from 'react-native'; const MyComponent = React.memo(({ data }) => { console.log('Rendering MyComponent'); return ( <View> <Text>Data: {JSON.stringify(data)}</Text> </View> ); }); export default MyComponent; """ ### 5.3. Batching API Requests **Standard:** Batch multiple API requests into a single request to reduce network overhead. * **Do This:** Use techniques like GraphQL or custom batching endpoints to combine multiple requests. * **Don't Do This:** Make numerous small API calls when the data can be retrieved in a single request. **Why:** Batching reduces the number of HTTP requests, improving performance, especially on slow or unreliable networks. ## 6. Security Best Practices ### 6.1. Input Validation **Standard:** Validate all user inputs before sending them to the API. * **Do This:** Use validation libraries (e.g., "yup", "joi") to validate input data and sanitize user input to prevent injection attacks. * **Don't Do This:** Trust user input without validation. **Why:** Input validation prevents malicious data from being sent to the server, protecting against security vulnerabilities. """javascript import * as yup from 'yup'; const schema = yup.object().shape({ username: yup.string().required().min(3).max(20), password: yup.string().required().min(8), email: yup.string().email().required(), }); const validateInput = async (data) => { try { await schema.validate(data, { abortEarly: false }); return { valid: true, errors: null }; } catch (error) { const errors = {}; error.inner.forEach(err => { errors[err.path] = err.message; }); return { valid: false, errors }; } }; // Example Usage const handleSubmit = async (data) => { const { valid, errors } = await validateInput(data); if (valid) { // Submit data to API try { await ApiService.post('register', data); } catch (error) { // Handle API error } } else { // Display validation errors console.log(errors); } }; """ ### 6.2. HTTPS **Standard:** Use HTTPS for all API requests to encrypt data in transit. * **Do This:** Ensure that all API endpoints use HTTPS. Configure your HTTP client to enforce HTTPS. * **Don't Do This:** Use HTTP for sensitive data transmission. **Why:** HTTPS protects data from eavesdropping and tampering during transmission. ### 6.3. Rate Limiting **Standard:** Implement rate limiting on the server-side to prevent abuse and denial-of-service attacks. * **Do This:** Monitor API usage and implement rate limiting to protect against abuse. * **Don't Do This:** Allow unlimited API requests from a single client. **Why:** Rate limiting prevents malicious users from overwhelming the server with excessive requests. This is enforced server-side, but understanding this interaction is important for client-side development. ## 7. Testing ### 7.1. Unit Testing **Standard:** Write unit tests for API service functions. * **Do This:** Use testing frameworks (e.g., Jest) to test API service functions in isolation. Mock API dependencies to ensure test stability. * **Don't Do This:** Skip unit testing for API integration logic. **Why:** Unit tests verify that API service functions work correctly and help prevent regressions. """javascript // ApiService.test.js import ApiService from './ApiService'; import axios from 'axios'; jest.mock('axios'); describe('ApiService', () => { it('should fetch data successfully', async () => { const mockData = { message: 'Success' }; axios.get.mockResolvedValue({ data: mockData }); const data = await ApiService.getData(); expect(data).toEqual(mockData); expect(axios.get).toHaveBeenCalledWith("${API_BASE_URL}/data"); }); it('should handle errors when fetching data', async () => { const mockError = new Error('Request failed'); axios.get.mockRejectedValue(mockError); await expect(ApiService.getData()).rejects.toThrow(mockError); expect(axios.get).toHaveBeenCalledWith("${API_BASE_URL}/data"); }); }); """ ### 7.2. Integration Testing **Standard:** Write integration tests to verify the interaction between the UI and the API. * **Do This:** Use testing frameworks (e.g., Detox, Appium) to test end-to-end scenarios involving API calls. * **Don't Do This:** Skip integration testing, which can lead to undetected issues in the API integration. **Why:** Integration tests verify that the UI and API work together correctly, ensuring a seamless user experience. ## 8. Monitoring and Logging ### 8.1. Logging API Requests and Responses **Standard:** Log API requests and responses for debugging and monitoring purposes. * **Do This:** Use logging libraries (e.g., "react-native-logs") to log API requests, responses, and errors. Be mindful of privacy considerations and avoid logging sensitive data. * **Don't Do This:** Ignore logging, which makes it difficult to diagnose and resolve issues. **Why:** Logging provides valuable insights into API usage and helps identify and resolve issues quickly. ### 8.2. Monitoring API Performance **Standard:** Monitor API performance metrics to identify bottlenecks and optimize performance. * **Do This:** Use monitoring tools (e.g., New Relic, Datadog) to track API response times, error rates, and resource usage. * **Don't Do This:** Ignore API performance, which can lead to slow and unresponsive applications. **Why:** Monitoring helps identify and resolve performance issues proactively, ensuring a smooth user experience. By adhering to these coding standards and best practices, React Native developers can create robust, efficient, and secure applications that integrate seamlessly with backend services and external APIs. This comprehensive guide serves as a valuable resource for professional development teams aiming to achieve high-quality code and maintainable applications. Remember to stay updated with the latest React Native features and adapt these standards as needed to suit your specific project requirements.