expo — Cursor rule: what it does, how to install
Enforces Expo and React Native best practices: directory layout, Expo Router conventions, TypeScript, and memoization patterns.
.cursor/rules/expo.mdcWhat expo does
This rule defines project architecture for Expo applications. Application logic belongs inside src/ partitioned into components, hooks, and services. Navigation relies strictly on the file-based app/ directory. It bans class components. All new code must be functional components written in TypeScript.
Styling requires StyleSheet.create instead of inline style objects. For performance, the rule enforces memoization via React.memo and useCallback. Asynchronous routines require explicit try/catch blocks, while a top-level ErrorBoundary handles rendering crashes.
Client-side variables must carry the EXPO_PUBLIC_ prefix in .env. Never place sensitive keys there: they bundle directly into client code, so server secrets belong in EAS Secrets.
What it helps with
- organize mobile projects around Expo Router file conventions
- eliminate inline styles in favor of StyleSheet
- prevent private API keys from leaking into client bundles
- wrap async operations in consistent try/catch blocks
Example prompts for the agent
- Create a dynamic profile screen in app/profile/[id].tsx using Expo Router
- Refactor this screen styles into a separate StyleSheet.create call
- Implement an async API request wrapped in a safe try/catch block
How to install expo
Manually: put the files in place
.cursor/rules/expo.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/expo.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/expo.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/expo.mdManually: put the files in place
.clinerules/expo.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about expo
How do I install expo in Cursor?
Save the rule contents into .cursor/rules/expo.mdc in the root of your project.
What does the expo rule do?
It guides Cursor to generate Expo and React Native code using TypeScript, Expo Router file conventions, immutable state updates, and React hooks.
How should secrets be handled under this rule?
Only non-sensitive client URLs belong in .env using EXPO_PUBLIC_. Private tokens and backend keys must stay out of the client bundle and go into EAS Secrets.
Source text
expo.mdc · CC0-1.0
---
description: This guide provides definitive, opinionated best practices for building robust, performant, and maintainable cross-platform mobile applications with Expo, leveraging modern React Native patterns and the latest SDK features.
globs: **/*
---
# expo Best Practices
This document outlines the definitive standards for Expo development. Adhere to these guidelines to ensure consistency, performance, and maintainability across our projects.
## 1. Code Organization and Structure
Maintain a clean, scalable project structure.
* **Root Structure:**
* `assets/`: Static assets (images, fonts).
* `src/`: All application logic.
* `src/components/`: Reusable UI components.
* `src/screens/`: Top-level components representing distinct app views.
* `src/navigation/`: Navigation configuration (if not using Expo Router's `app/`).
* `src/hooks/`: Custom React Hooks.
* `src/utils/`: Utility functions, constants.
* `src/services/`: API clients, data fetching logic.
* **Naming Conventions:**
* **Files/Folders:** `kebab-case` for directories, `PascalCase` for components, `camelCase` for hooks/utils.
* **Components:** `PascalCase` (e.g., `Button.tsx`, `HomeScreen.tsx`).
* **Variables/Functions:** `camelCase` (e.g., `userName`, `fetchData`).
* **Constants:** `UPPER_SNAKE_CASE` for global constants.
## 2. Component Best Practices
Prioritize functional components, TypeScript, and clear styling.
* **Functional Components with Hooks:** Always use functional components.
❌ BAD:
```typescript
class MyComponent extends React.Component { /* ... */ }
```
✅ GOOD:
```typescript
const MyComponent: React.FC = () => { /* ... */ };
```
* **TypeScript for Type Safety:** All new code must be in TypeScript.
❌ BAD:
```javascript
function greet(name) { return `Hello, ${name}`; }
```
✅ GOOD:
```typescript
const greet = (name: string): string => `Hello, ${name}`;
```
* **Styling with `StyleSheet`:** Centralize styles for readability and performance.
❌ BAD:
```typescript
<Text style={{ fontSize: 16, color: 'blue' }}>Hello</Text>
```
✅ GOOD:
```typescript
import { StyleSheet, Text } from 'react-native';
const MyComponent = () => <Text style={styles.text}>Hello</Text>;
const styles = StyleSheet.create({ text: { fontSize: 16, color: 'blue' } });
```
## 3. Navigation with Expo Router
Leverage file-based routing for intuitive navigation.
* **File-System Based Routing:** Use `app/` directory for routing.
❌ BAD: Manual stack/tab navigator setup in a single file for all routes.
✅ GOOD:
```
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return <Stack />;
}
// app/index.tsx (maps to /)
export default function HomePage() { /* ... */ }
// app/profile/[id].tsx (maps to /profile/:id)
export default function ProfilePage() {
const { id } = useLocalSearchParams();
// ...
}
```
## 4. Data & State Management
Manage state efficiently and immutably.
* **Immutable State Updates:** Always create new objects/arrays when updating state.
❌ BAD:
```typescript
const [user, setUser] = useState({ name: 'Alice' });
user.name = 'Bob'; // Direct mutation
setUser(user);
```
✅ GOOD:
```typescript
const [user, setUser] = useState({ name: 'Alice' });
setUser(prev => ({ ...prev, name: 'Bob' }));
```
* **`useEffect` Dependency Arrays:** Carefully manage dependencies to prevent unnecessary re-renders or infinite loops.
❌ BAD:
```typescript
useEffect(() => { fetchData(); }); // Runs on every render
```
✅ GOOD:
```typescript
useEffect(() => { fetchData(); }, []); // Runs once on mount
useEffect(() => { saveUser(user); }, [user]); // Runs when user changes
```
## 5. Environment Variables
Securely manage environment-specific values.
* **`EXPO_PUBLIC_` Prefix:** Use `.env` files with `EXPO_PUBLIC_` for client-side variables.
❌ BAD: `API_KEY=mysecret` in `.env` and `process.env.API_KEY`. (Exposed in bundle, not automatically injected)
✅ GOOD:
```
// .env
EXPO_PUBLIC_API_URL=https://api.example.com/staging
```
```typescript
// In your code
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
```
> **Warning:** Never store sensitive keys (e.g., private API keys) in `EXPO_PUBLIC_` variables. They are bundled client-side. Use EAS Secrets for server-side secrets.
## 6. Performance Considerations
Optimize for a smooth user experience.
* **`React.memo` for Pure Components:** Wrap pure functional components to prevent re-renders when props are unchanged.
❌ BAD:
```typescript
const MyItem = ({ data }) => { /* ... */ }; // Renders even if data is same object reference
```
✅ GOOD:
```typescript
const MyItem = React.memo(({ data }) => { /* ... */ });
```
* **`useCallback` and `useMemo`:** Memoize functions and values passed to `React.memo` components or expensive computations.
❌ BAD:
```typescript
const handlePress = () => { /* ... */ }; // New function on every render
<MyItem onPress={handlePress} />
```
✅ GOOD:
```typescript
const handlePress = useCallback(() => { /* ... */ }, []);
<MyItem onPress={handlePress} />
```
* **Lazy Loading Screens:** For large apps, lazy load screens with `React.lazy` and `Suspense`.
```typescript
const LazyScreen = React.lazy(() => import('./LazyScreen'));
// In your navigation or component:
<Suspense fallback={<LoadingSpinner />}>
<LazyScreen />
</Suspense>
```
## 7. Error Handling
Implement robust error handling mechanisms.
* **`try/catch` for Async Operations:** Handle potential errors in asynchronous code.
❌ BAD:
```typescript
const fetchData = async () => { await api.get('/data'); };
```
✅ GOOD:
```typescript
const fetchData = async () => {
try {
await api.get('/data');
} catch (error) {
console.error('Failed to fetch data:', error);
// Display user-friendly error message
}
};
```
* **Global Error Boundary:** Catch UI errors in React components.
```typescript
// src/components/ErrorBoundary.tsx
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
render() {
if (this.state.hasError) { return <Text>Something went wrong.</Text>; }
return this.props.children;
}
}
// In App.tsx or _layout.tsx
<ErrorBoundary><App /></ErrorBoundary>
```
## 8. Testing Approaches
Ensure code quality with comprehensive testing.
* **Unit Tests with Jest & React Testing Library:** Focus on component logic and user interactions. Aim for ≥80% coverage.
❌ BAD: No tests, or only shallow rendering tests.
✅ GOOD:
```typescript
// src/components/Button.test.tsx
import { render, fireEvent } from '@testing-library/react-native';
import Button from './Button';
test('renders correctly and calls onPress', () => {
const mockOnPress = jest.fn();
const { getByText } = render(<Button title="Click Me" onPress={mockOnPress} />);
fireEvent.press(getByText('Click Me'));
expect(mockOnPress).toHaveBeenCalledTimes(1);
});
```
* **Snapshot Testing for UI Components:** Capture UI structure to detect unintended changes.
```typescript
// src/components/Button.test.tsx (continued)
import renderer from 'react-test-renderer';
test('renders correctly (snapshot)', () => {
const tree = renderer.create(<Button title="Test" onPress={() => {}} />).toJSON();
expect(tree).toMatchSnapshot();
});
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc