# State Management Standards for React Native
This document outlines the coding standards for state management in React Native applications. It provides guidelines for choosing appropriate state management solutions, structuring data flow, and writing maintainable and performant code.
## 1. Guiding Principles
* **Single Source of Truth:** Each piece of application state should have a single, authoritative source. Avoid duplicating state across multiple components, as this leads to inconsistencies and difficulties in maintaining data integrity.
* **Predictable State Updates:** State updates should be predictable and traceable. Favor immutable data structures and pure functions to ensure that state changes are easily understood and debugged.
* **Component Isolation:** Promote component isolation by only providing components with the data they require. Use techniques like prop drilling through context or selectors with global state management solutions to avoid unnecessary re-renders.
* **Performance:** Optimize state updates and rendering to ensure a smooth user experience. Use memoization techniques, lazy loading of data, and efficient data structures to minimize performance bottlenecks.
* **Testability:** Adhere to principles that allow for adequate testing on state management and data handling logic.
* **Data serialization:** Ensure proper serialzation/deserialization of local storage and network layer for data consistency.
## 2. State Management Options
Choosing the right state management solution is crucial for building scalable React Native applications. Consider the following options:
### 2.1 "useState" and "useReducer" (Local Component State)
* **Use Case:** Managing simple, component-local state.
* **Do This:**
* Use "useState" for simple state variables that change infrequently.
* Use "useReducer" for managing more complex state logic, especially when the next state depends on the previous state.
* **Don't Do This:**
* Overuse "useState" in deeply nested components where the state could be managed more effectively at a higher level.
* Mutate state directly. Always use the setter function provided by "useState" or "useReducer".
"""javascript
// useState example
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
const Counter = () => {
const [count, setCount] = useState(0);
return (
Count: {count}
setCount(count + 1)} />
);
};
export default Counter;
// useReducer example
import React, { useReducer } from 'react';
import { View, Text, Button } from 'react-native';
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 CounterReducer = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
Count: {state.count}
dispatch({ type: 'increment' })} />
dispatch({ type: 'decrement' })} />
);
};
export default CounterReducer;
"""
#### 2.1.1 "useReducer" for Complex State Transitions
For state logic that requires more structured updates, utilize "useReducer". It allows you to define a reducer function that dictates how the state should change based on actions.
"""javascript
import React, { useReducer } from 'react';
import { View, Text, Button } from 'react-native';
const initialTodos = [
{ id: 1, text: 'Learn Reducer', completed: false },
];
const todosReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, completed: false }];
case 'TOGGLE_TODO':
return state.map((todo) =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
default:
return state;
}
};
const Todos = () => {
const [todos, dispatch] = useReducer(todosReducer, initialTodos);
const addTodo = () => {
dispatch({ type: 'ADD_TODO', text: 'New Todo' });
};
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', id });
};
return (
{todos.map((todo) => (
toggleTodo(todo.id)} />
))}
);
};
"""
#### 2.1.2 Avoiding Direct Mutation
Always return a new state object from the reducer. Avoid direct mutation of the state, as it can lead to unexpected behavior and performance problems.
"""javascript
// Do this:
const reducer = (state, action) => {
return { ...state, value: action.payload }; // Create a new object
};
// Don't do this:
const reducer = (state, action) => {
state.value = action.payload; // Direct mutation
return state;
};
"""
### 2.2 Context API (Global Component State)
* **Use Case:** Sharing state between components without prop drilling. Can also be used as simplistic global state management and dependency injection.
* **Do This:**
* Use "Context.Provider" to wrap the part of your component tree that needs access to the context.
* Use "useContext" hook to consume the context value in functional components.
* Create custom hooks that wrap "useContext" for more maintainable and reusable code.
* **Don't Do This:**
* Store large amounts of rapidly changing data in context, as it can cause unnecessary re-renders. Consider using a more optimized global state management solution such as Redux or Zustand for such cases.
* Mutate the context value directly. Always provide a mechanism for updating the context value through a provider function.
"""javascript
// Create a context
import React, { createContext, useState, useContext } from 'react';
import { View, Text, Button } from 'react-native';
const ThemeContext = createContext();
// Create a provider
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
{children}
);
};
// Custom Hook that wraps useContext
const useTheme = () => {
return useContext(ThemeContext);
};
// Consumer component
const ThemedComponent = () => {
const { theme, toggleTheme } = useTheme();
return (
Current Theme: {theme}
);
};
// App component
const App = () => {
return (
);
};
export default App;
"""
#### 2.2.1 Value stability for Context
* **Do This:** Ensure stability of provided value for context. For example, when the context becomes too large, provide a memoized function.
"""javascript
// Stable context provider, wrapping theme and toggler together.
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
// Memoize toggleTheme to prevent unnecessary re-renders
const toggleTheme = useCallback(() => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
}, []);
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
return (
{children}
);
};
"""
### 2.3 Redux (Global State Management)
* **Use Case:** Managing complex application state, especially when state is shared across many components and requires predictable updates.
* **Do This:**
* Use Redux Toolkit to simplify Redux setup and reduce boilerplate code.
* Structure your state into logical slices.
* Use selectors to efficiently derive data from the store.
* Favor asynchronous actions with Redux Thunk or Redux Saga for handling side effects.
* **Don't Do This:**
* Use Redux for simple applications where component-local state or Context API would suffice.
* Mutate state directly in reducers. Always return a new state object.
* Put non-serializable data in the Redux store (functions, class instances, etc.).
"""javascript
// Redux Toolkit setup
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
import React from 'react';
import { View, Text, Button } from 'react-native';
// Create a slice
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
// Export actions and reducer
export const { increment, decrement } = counterSlice.actions;
const counterReducer = counterSlice.reducer;
// Create the store
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
// Component using Redux
const CounterRedux = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
Count: {count}
dispatch(increment())} />
dispatch(decrement())} />
);
};
// Root component
const App = () => {
return (
);
};
export default App;
"""
#### 2.3.1 Redux Thunk for Asynchronous Actions
Use Redux Thunk middleware to handle asynchronous actions, such as fetching data from an API.
"""javascript
// Example using Redux Thunk
import { createAsyncThunk } from '@reduxjs/toolkit';
// Async thunk action
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId, { rejectWithValue }) => {
try {
const response = await fetch("https://api.example.com/users/${userId}");
if (!response.ok) {
// Handle HTTP errors
return rejectWithValue("HTTP error! status: ${response.status}");
}
const data = await response.json();
return data;
} catch (error) {
// Handle network errors
return rejectWithValue(error.message);
}
}
);
// Update the slice
const userSlice = createSlice({
name: 'user',
initialState: { user: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.user = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload; // Use custom error message from the thunk
});
},
});
"""
#### 2.3.2 Selectors
Use selectors (e.g., Reselect) to derive data from the Redux store efficiently. Selectors memoize their results, preventing unnecessary recomputations and re-renders.
"""javascript
// Example using Reselect
import { createSelector } from '@reduxjs/toolkit';
import { useSelector } from 'react-redux';
const selectCounterValue = (state) => state.counter.value;
export const selectDoubleCounterValue = createSelector(
[selectCounterValue],
(value) => value * 2
);
const MyComponent = () => {
const doubleCount = useSelector(selectDoubleCounterValue);
return Double Count: {doubleCount};
};
"""
### 2.4 Zustand (Global State Management)
* **Use Case:** Simpler alternative to Redux for global state management with a minimal API.
* **Do This:**
* Define your store using the "create" function.
* Use the "set" function to update state.
* Use selectors to access state in components.
* **Don't Do This:**
* Overcomplicate your store logic. Zustand is designed to be simple.
* Mutate state directly. Always use the "set" function.
"""javascript
// Zustand store
import create from 'zustand';
import { View, Text, Button } from 'react-native';
import React from 'react';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// Component using Zustand
const CounterZustand = () => {
const { count, increment, decrement } = useStore((state) => ({
count: state.count,
increment: state.increment,
decrement: state.decrement,
}));
return (
Count: {count}
);
};
export default CounterZustand;
"""
### 2.5 Jotai (Global State Management)
* **Use Case:** Atomic state management library, suitable for fine-grained state updates with optimized re-renders.
* **Do This:**
* Define atoms for individual pieces of state.
* Use the "useAtom" hook to read and update atoms in components.
* Compose atoms to derive computed values.
* **Don't Do This:**
* Create too many atoms for closely related state, which can make your code harder to understand.
* Overuse derived atoms, which can lead to performance issues if they are not properly memoized.
"""javascript
// Jotai atoms
import { atom, useAtom } from 'jotai';
import { View, Text, Button } from 'react-native';
import React from 'react';
const countAtom = atom(0);
const CounterJotai = () => {
const [count, setCount] = useAtom(countAtom);
return (
Count: {count}
setCount(count + 1)} />
setCount(count - 1)} />
);
};
export default CounterJotai;
"""
#### 2.5.1 Derived atoms in Jotai
Derived atoms are a powerful way to compute new atoms based on other atoms.
"""javascript
import { atom, useAtom } from 'jotai';
const basePriceAtom = atom(10);
const discountAtom = atom(0.2);
const discountedPriceAtom = atom((get) => {
const basePrice = get(basePriceAtom);
const discount = get(discountAtom);
return basePrice * (1 - discount);
});
const PriceDisplay = () => {
const [discountedPrice] = useAtom(discountedPriceAtom);
return (
Discounted Price: {discountedPrice}
);
};
"""
## 3. Data Fetching and API Integration
* **Use Case:** Data fetching from REST APIs and GraphQL APIs.
* **Do This:**
* Use libraries like "axios" or the built-in "fetch" API for making HTTP requests.
* Use "useEffect" hook to fetch data when the component mounts.
* Implement proper error handling and loading states for a better user experience.
* Use caching mechanisms to avoid redundant API calls.
* Consider using libraries like "react-query" or "swr" for simplified data fetching and caching.
* **Don't Do This:**
* Make API calls directly in the render function, as this can lead to performance issues and infinite loops.
* Ignore error handling, which can result in unexpected app behavior.
* Store sensitive API keys directly in the code. Use environment variables.
"""javascript
// Data fetching with useEffect
import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
const DataFetcher = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios('https://api.example.com/data');
setData(result.data);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
};
fetchData();
}, []); // Empty dependency array to run only on mount
if (loading) {
return Loading...;
}
if (error) {
return Error: {error.message};
}
return (
Data: {JSON.stringify(data)}
);
};
export default DataFetcher;
"""
#### 3.1.1 Using "react-query" for Data Fetching and Caching
"react-query" simplifies data fetching, caching, and updating in React applications.
"""javascript
import { useQuery } from 'react-query';
import { View, Text } from 'react-native';
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const DataFetcherWithReactQuery = () => {
const { data, isLoading, error } = useQuery('myData', fetchData);
if (isLoading) {
return Loading...;
}
if (error) {
return Error: {error.message};
}
return (
Data: {JSON.stringify(data)}
);
};
export default DataFetcherWithReactQuery;
"""
## 4. Updating and Transforming State
* **Use Case:** Correctly updating and transforming state in React Native components. Focus on immutability.
* **Do This:**
* Use immutable update patterns to avoid unexpected side effects.
* When updating arrays or objects in state, create new copies instead of modifying the original objects.
* Use the spread operator ("...") to create shallow copies of objects and arrays.
* Use libraries like "immer" for simplifying immutable updates with deeply nested objects.
* **Don't Do This:**
* Mutate state directly (e.g., "state.push(newItem)").
* Forget to update nested objects immutably.
"""javascript
// Immutable updates
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
const ImmutableUpdate = () => {
const [items, setItems] = useState([{ id: 1, name: 'Item 1' }]);
const addItem = () => {
setItems([...items, { id: Date.now(), name: 'New Item' }]);
};
const updateItem = (id, newName) => {
setItems(
items.map((item) => (item.id === id ? { ...item, name: newName } : item))
);
};
return (
{items.map((item) => (
{item.name}
))}
updateItem(1, 'Updated Item')} />
);
};
export default ImmutableUpdate;
"""
### 4.1 Using Immer for Complex Immutable Updates
"immer" simplifies working with immutable data structures, especially when dealing with deeply nested objects.
"""javascript
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { useImmer } from 'use-immer';
const ImmerExample = () => {
const [state, updateState] = useImmer({
user: {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Anytown',
},
},
});
const updateCity = () => {
updateState((draft) => {
draft.user.address.city = 'Newtown';
});
};
return (
Name: {state.user.name}
City: {state.user.address.city}
);
};
"""
## 5. Performance Optimization
* **Use Case:** Optimizing React Native state management for smooth UI.
* **Do This:**
* Use "React.memo" to prevent unnecessary re-renders of components.
* Use "useCallback" to memoize event handlers.
* Use "useMemo" to memoize expensive computations.
* Virtualize long lists with "FlatList" or "SectionList" to improve scrolling performance. Avoid "map" on large arrays where possible.
* Avoid unnecessary state updates by comparing previous and next values.
* **Don't Do This:**
* Overuse "React.memo", "useCallback", and "useMemo", as they can add unnecessary overhead if not used judiciously.
* Update state frequently with large amounts of data, as it can cause performance bottlenecks.
"""javascript
// Memoization with React.memo
import React from 'react';
import { View, Text } from 'react-native';
const MyComponent = ({ data }) => {
console.log('MyComponent rendered');
return Data: {data};
};
const MemoizedComponent = React.memo(MyComponent);
// Memoization with useCallback
import React, { useCallback } from 'react';
import { Button } from 'react-native';
const MyButton = ({ onClick }) => {
console.log('MyButton rendered');
return ;
};
const MemoizedButton = React.memo(MyButton);
const ParentComponent = () => {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return ;
};
// Virtualized list
import React from 'react';
import { FlatList, Text, View } from 'react-native';
const data = Array.from({ length: 1000 }, (_, i) => ({ id: i, text: "Item ${i}" }));
const Item = React.memo(({ text }) => {
console.log("Item ${text} rendered");
return {text};
});
const MyList = () => {
const renderItem = ({ item }) => ;
return (
item.id.toString()}
/>
);
};
"""
## 6. Testing State Management
* **Use Case:** Testing React Native state management code thoroughly.
* **Do This:**
* Write unit tests for reducers, actions, and selectors.
* Use mock stores and providers to isolate components during testing.
* Test side effects (e.g., API calls) using mock libraries like "axios-mock-adapter".
* Write integration tests to verify the interaction between components and state management logic.
* Mock API calls for testing the data fetching and success and failure states.
* **Don't Do This:**
* Skip testing state management logic.
* Write brittle tests that rely on implementation details rather than behavior.
"""javascript
// Example using Jest and React Testing Library with React Hook Form
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { useForm } from 'react-hook-form';
// Test component
const MyForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
return (
{errors.name && {errors.name.message}}
);
};
// Test case
it('should display an error message if name is not entered', () => {
render();
fireEvent.press(screen.getByText('Submit'));
expect(screen.getByText('Name is required')).toBeTruthy();
});
it('should submit the form with valid data', () => {
const alertMock = jest.spyOn(window, 'alert'); // Mock the alert function
render();
const nameInput = screen.getByPlaceholderText('Name');
fireEvent.changeText(nameInput, 'John Doe');
fireEvent.press(screen.getByText('Submit'));
expect(alertMock).toHaveBeenCalledWith(JSON.stringify({ name: 'John Doe' })); // Check if the alert was called with the correct data
alertMock.mockRestore(); // Restore alert to original function
});
"""
## 7. Security Considerations
* **Use Case:** State Management security standards for React Native
* **Do This:**
* Avoid storing sensitive data directly in state management solutions that can be easily accessed (e.g., tokens, passwords, etc.).
* Encrypt sensitive data before storing it in local storage or global state.
* Sanitize user inputs before updating the state to prevent XSS attacks.
* Implement proper authentication and authorization mechanisms to protect sensitive API endpoints.
* **Don't Do This:**
* Store sensitive information in plain text.
* Trust user input without validation and sanitization.
* Expose sensitive data in client-side code.
## 8. Conclusion
Adhering to these standards can significantly improve the quality, maintainability, and performance of React Native applications. By selecting the right architecture and tools, and writing clean, testable code, developers can create robust and scalable solutions that deliver a great user experience.
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.
# 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 ( <View style={styles.container}> <ActivityIndicator size="large" color="#0000ff" /> </View> ); } return ( <View style={styles.container}> <Text style={styles.text}>{text}</Text> </View> ); }; 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 ( <View style={styles.container}> <Text style={styles.text}>{this.state.message}</Text> </View> ); } } 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 }) => ( <TouchableOpacity style={[styles.button, style]} onPress={onPress}> <Text style={styles.buttonText}>{title}</Text> </TouchableOpacity> ); // Card Component using Button const Card = ({ children, title }) => ( <View style={styles.card}> <Text style={styles.cardTitle}>{title}</Text> {children} </View> ); const MyScreen = () => ( <View> <Card title="Welcome!"> <Text>This is content inside the card.</Text> <Button title="Click Me" onPress={() => alert('Button Pressed!')} /> </Card> </View> ); 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 ? <Text>Loading data...</Text> : render(data); }; // Usage: const UserList = () => ( <DataProvider url="https://jsonplaceholder.typicode.com/users" render={users => ( <ScrollView> {users.map(user => ( <Text key={user.id}>{user.name}</Text> ))} </ScrollView> )} /> ); 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 }) => ( <View style={[styles.container, style]}> <Text style={styles.text}>Name: {name}</Text> <Text style={styles.text}>Age: {age}</Text> <Text style={styles.text}>Is Admin: {isAdmin ? 'Yes' : 'No'}</Text> </View> ); 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<ViewStyle>; } const MyTypescriptComponent: React.FC<Props> = ({ name, age = 18, isAdmin = false, style }) => ( <View style={[styles.container, style]}> <Text style={styles.text}>Name: {name}</Text> <Text style={styles.text}>Age: {age}</Text> <Text style={styles.text}>Is Admin: {isAdmin ? 'Yes' : 'No'}</Text> </View> ); 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 ( <View style={styles.container}> <Text style={styles.text}>Count: {count}</Text> <Button title="Increment" onPress={increment} /> <Button title="Decrement" onPress={decrement} /> </View> ); }; 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 ( <View style={styles.container}> <Text style={styles.text}>Count: {state.count}</Text> <Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} /> <Button title="Decrement" onPress={() => dispatch({ type: 'DECREMENT' })} /> </View> ); }; """ ### 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 }) => ( <View style={styles.container}> <Text style={styles.text}>{text}</Text> </View> ); 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 ( <View style={[styles.container, { backgroundColor: theme.colors.background }]}> <Text style={[styles.text, { color: theme.colors.text }]}>Themed Text</Text> </View> ); }; 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 ( <View style={styles.container}> <ActivityIndicator size="large" color="#0000ff" /> </View> ); } if (error) { return ( <View style={styles.container}> <Text style={styles.text}>Error: {error.message}</Text> </View> ); } return ( <View style={styles.container}> <Text style={styles.text}>Title: {data.title}</Text> <Text style={styles.text}>Completed: {data.completed ? 'Yes' : 'No'}</Text> </View> ); }; 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 ( <View style={styles.container}> <Text style={styles.text}>{text}</Text> </View> ); }; 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 ( <Button title={"Item: ${data.value}"} onPress={onClick} /> ); }); 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 ( <View> <Text>Count: {count}</Text> <Button title="Increment" onPress={() => setCount(count + 1)} /> <ExpensiveComponent data={data} onClick={handleClick} /> </View> ); }; """ ### 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 }) => ( <View style={styles.item}> <Text style={styles.title}>{title}</Text> </View> ); const MyListComponent = () => { const renderItem = ({ item }) => ( <Item title={item.value} /> ); return ( <FlatList data={data} renderItem={renderItem} keyExtractor={item => 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 = () => ( <View style={styles.container}> <Image style={styles.image} source={{ uri: 'https://via.placeholder.com/150', }} resizeMode="cover" //Choose an appropriate resizeMode /> </View> ); 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 = () => ( <View style={styles.container}> <FastImage style={styles.image} source={{ uri: 'https://via.placeholder.com/150', headers: { Authorization: 'someAuthToken' }, priority: FastImage.priority.normal, }} resizeMode={FastImage.resizeMode.contain} /> </View> ) """ ## 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.
# 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.