Recuperare il Cheatsheet Nativo¶
React Native - Imparare una volta, scrivere ovunque__HTML_TAG_70_
__HTML_TAG_71_
React Native è un framework per la costruzione di applicazioni mobili native utilizzando React. Consente agli sviluppatori di utilizzare React insieme alle capacità di piattaforma native per costruire applicazioni mobili per iOS e Android utilizzando un unico codebase.
Traduzione:
__HTML_TAG_73_
__HTML_TAG_74_
__HTML_TAG_79_
__HTML_TAG_80_
## Tavola dei contenuti
- [Installazione](#installazione)
[Getting Started](#getting-started)
- [Core Components](#core-components)
[Navigazione]
- [Gestione dello stato](#gestione dello stato)
[Styling](#styling)
- [API e Servizi](#apis-and-services)
- [Moduli Native](#native-moduli)
- [Performance](#performance)
[Testing](#testing)
- [Debugging]
- [Deployment](#deployment)
- [Platform-Specific Code](#platform-specific-code)
- [Librarie di terze parti]
[Le migliori pratiche](#best-practices)
- [Troubleshooting](#troubleshooting)
## Installazione
### Development Environment Setup
### Prerequisiti
Traduzione:
#### Reazione CLI
Traduzione:
#### Android Development Setup
Traduzione:
#### iOS Development Setup (macOS only)
Traduzione:
## # Project Creation
#### Reazione CLI
Traduzione:
#### Expo CLI
Traduzione:
# Eseguire l'app
#### Reazione CLI
Traduzione:
#### Expo
Traduzione:
#
### Basic App Structure
Traduzione:
### DigiScript Setup
Traduzione:
// tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"lib": ["es2017", "es7", "es6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-native"
},
"include": [
"src/**/*",
"App.tsx"
],
"exclude": [
"node_modules"
]
}
### Struttura del progetto
Traduzione:
## Componenti core
### Componenti di base
#
### Componenti avanzati
Traduzione:
### Componenti personalizzati
Traduzione:
## Navigazione
### React Navigation Setup
Traduzione:
## Stack Navigator #
Traduzione:
## Tab Navigator
Traduzione:
### Navigazione ganci
Traduzione:
## Drawer Navigator
Traduzione:
## State Management
# Agganci reattivi
Traduzione:
### Context API
Traduzione:
### Redux Setup
Traduzione:
// store/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import authReducer from './authSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
auth: authReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// Usage in component
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';
const CounterScreen = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<View style={styles.container}>
<Text style={styles.countText}>Count: {count}</Text>
<Button title="+" onPress={() => dispatch(increment())} />
<Button title="-" onPress={() => dispatch(decrement())} />
<Button title="+5" onPress={() => dispatch(incrementByAmount(5))} />
</View>
);
};
// App.js with Redux Provider
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store/store';
import AppNavigator from './navigation/AppNavigator';
const App = () => {
return (
<Provider store={store}>
<AppNavigator />
</Provider>
);
};
export default App;
# Styling #
### Style Foglio
Traduzione:
## Flexbox Layout
Traduzione:
### Responsive Design
Traduzione:
### Styled Components
Traduzione:
import React from 'react';
import styled from 'styled-components/native';
const Container = styled.View`
flex: 1;
padding: 20px;
background-color: #f5f5f5;
`;
const Title = styled.Text`
font-size: 24px;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
color: #333;
`;
const StyledButton = styled.TouchableOpacity`
background-color: ${props => props.primary ? '#007AFF' : '#FF3B30'};
padding: 15px 30px;
border-radius: 10px;
margin-bottom: 10px;
align-items: center;
`;
const ButtonText = styled.Text`
color: white;
font-size: 16px;
font-weight: 600;
`;
const StyledComponentsExample = () => {
return (
<Container>
<Title>Styled Components</Title>
<StyledButton primary onPress={() => console.log('Primary pressed')}>
<ButtonText>Primary Button</ButtonText>
</StyledButton>
<StyledButton onPress={() => console.log('Secondary pressed')}>
<ButtonText>Secondary Button</ButtonText>
</StyledButton>
</Container>
);
};
export default StyledComponentsExample;
## APIs and Services
### Fetch API
Traduzione:
### Axios Integrazione
Traduzione:
// services/httpClient.js
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
const httpClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
httpClient.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor
httpClient.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
if (error.response?.status === 401) {
// Handle unauthorized access
await AsyncStorage.removeItem('authToken');
// Navigate to login screen
}
return Promise.reject(error);
}
);
export default httpClient;
// services/userService.js
import httpClient from './httpClient';
export const userService = {
getUsers: () => httpClient.get('/users'),
getUser: (id) => httpClient.get(`/users/${id}`),
createUser: (userData) => httpClient.post('/users', userData),
updateUser: (id, userData) => httpClient.put(`/users/${id}`, userData),
deleteUser: (id) => httpClient.delete(`/users/${id}`),
};
## AsyncStorage
Traduzione:
// utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storage = {
// Store data
setItem: async (key, value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
} catch (error) {
console.error('Error storing data:', error);
}
},
// Retrieve data
getItem: async (key) => {
try {
const jsonValue = await AsyncStorage.getItem(key);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (error) {
console.error('Error retrieving data:', error);
return null;
}
},
// Remove data
removeItem: async (key) => {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.error('Error removing data:', error);
}
},
// Clear all data
clear: async () => {
try {
await AsyncStorage.clear();
} catch (error) {
console.error('Error clearing storage:', error);
}
},
// Get all keys
getAllKeys: async () => {
try {
return await AsyncStorage.getAllKeys();
} catch (error) {
console.error('Error getting keys:', error);
return [];
}
},
};
// Usage in component
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import { storage } from '../utils/storage';
const StorageExample = () => {
const [name, setName] = useState('');
const [savedName, setSavedName] = useState('');
useEffect(() => {
loadSavedName();
}, []);
const loadSavedName = async () => {
const saved = await storage.getItem('userName');
if (saved) {
setSavedName(saved);
}
};
const saveName = async () => {
await storage.setItem('userName', name);
setSavedName(name);
setName('');
};
const clearName = async () => {
await storage.removeItem('userName');
setSavedName('');
};
return (
<View style={styles.container}>
<Text style={styles.title}>AsyncStorage Example</Text>
<TextInput
style={styles.input}
placeholder="Enter your name"
value={name}
onChangeText={setName}
/>
<Button title="Save Name" onPress={saveName} />
<Button title="Clear Name" onPress={clearName} />
{savedName ? (
<Text style={styles.savedText}>Saved Name: {savedName}</Text>
) : (
<Text style={styles.noDataText}>No saved name</Text>
)}
</View>
);
};
## Moduli nativi
### Accessing Device Features
#
### Creare moduli nativi personalizzati
Traduzione:
## Performance
### Tecniche di ottimizzazione
Traduzione:
### Ottimizzazione FlatList
Traduzione:
### Ottimizzazione immagine
Traduzione:
#### Ottimizzazione delle dimensioni di Bundle
Traduzione:
## Testing
# Jest Setup
Traduzione:
// __tests__/App.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import '@testing-library/jest-native/extend-expect';
import App from '../App';
describe('App', () => {
it('renders correctly', () => {
const { getByText } = render(<App />);
expect(getByText('Welcome to React Native!')).toBeTruthy();
});
it('handles button press', async () => {
const { getByText, getByTestId } = render(<App />);
const button = getByTestId('test-button');
fireEvent.press(button);
await waitFor(() => {
expect(getByText('Button pressed!')).toBeTruthy();
});
});
});
// Component testing
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import CustomButton from '../components/CustomButton';
describe('CustomButton', () => {
it('calls onPress when pressed', () => {
const mockOnPress = jest.fn();
const { getByText } = render(
<CustomButton title="Test Button" onPress={mockOnPress} />
);
fireEvent.press(getByText('Test Button'));
expect(mockOnPress).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
const { getByText } = render(
<CustomButton title="Test Button" disabled={true} />
);
const button = getByText('Test Button').parent;
expect(button).toBeDisabled();
});
});
### Detox E2E Testing
Traduzione:
// e2e/firstTest.e2e.js
describe('Example', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
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();
});
});
// .detoxrc.json
{
"testRunner": "jest",
"runnerConfig": "e2e/config.json",
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/YourApp.app",
"build": "xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 12"
}
},
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_4_API_30"
}
}
}
}
## Mocking #
Traduzione:
# Debug #
# React Debugger nativo
Traduzione:
### Flipper Integration
Traduzione:
// utils/flipper.js
import { logger } from 'react-native-logs';
const defaultConfig = {
severity: __DEV__ ? 'debug' : 'error',
transport: __DEV__ ? logger.consoleTransport : logger.fileAsyncTransport,
transportOptions: {
colors: {
info: 'blueBright',
warn: 'yellowBright',
error: 'redBright',
},
},
};
const log = logger.createLogger(defaultConfig);
export default log;
// Usage in components
import log from '../utils/flipper';
const MyComponent = () => {
const handlePress = () => {
log.debug('Button pressed');
log.info('User interaction logged');
};
return (
<Button title="Press me" onPress={handlePress} />
);
};
### Performance Monitoring
Traduzione:
### Error Boundas
Traduzione:
#
### Android Deployment
Traduzione:
// android/app/build.gradle
android {
...
signingConfigs {
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}
// android/gradle.properties
MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=my-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=*****
MYAPP_UPLOAD_KEY_PASSWORD=*****
### iOS Deployment
Traduzione:
### Fastlane Automation
Traduzione:
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Build and upload to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp")
upload_to_testflight
end
desc "Build and upload to App Store"
lane :release do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp")
upload_to_app_store
end
end
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Build and upload to Play Console"
lane :beta do
gradle(task: "clean bundleRelease")
upload_to_play_store(track: 'beta')
end
desc "Deploy to Play Store"
lane :release do
gradle(task: "clean bundleRelease")
upload_to_play_store
end
end
### CodePush (Over-the-Air Updates)
Traduzione:
// App.js with CodePush
import codePush from 'react-native-code-push';
const App = () => {
return (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
};
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESUME,
};
export default codePush(codePushOptions)(App);
// Release update
// code-push release-react MyApp-iOS ios
// code-push release-react MyApp-Android android
## Platform-Specific Code
### Modulo della piattaforma
Traduzione:
### Platform-Specific Files
Traduzione:
# Gestione delle aree sicure
Traduzione:
# Biblioteche di terze parti
### Librerie popolari
Traduzione:
### Esempi di integrazione della libreria
Traduzione:
# Migliori Pratiche
### Code Organization
Traduzione:
## Performance Best Practices
Traduzione:
### Migliori pratiche di sicurezza
Traduzione:
## Risoluzione dei problemi
### Questioni comuni
Traduzione:
## Debug Errori comuni
Traduzione:
---
## Sommario
React Native è un potente framework per la costruzione di applicazioni mobili cross-platform che fornisce:
- **Cross-Platform Development**: Scrivere una volta, eseguire su iOS e Android
- **Native Performance**: Accesso diretto alle API e ai componenti della piattaforma nativa
- **Hot Reloading**: ciclo di sviluppo rapido con feedback istantanei
- **Rich Ecosystem**: Ampio ecosistema bibliotecario e supporto comunitario
- **Familiar Development**: Utilizza i concetti di React e JavaScript/TypeScript
- ** Architettura flessibile**: Supporta diverse soluzioni di gestione e navigazione dello stato
- **Production Ready**: Usato da grandi aziende come Facebook, Instagram e Airbnb
React Native eccelle per consentire uno sviluppo rapido delle app mobili mantenendo le prestazioni native e le esperienze utente specifiche della piattaforma. Il suo ecosistema maturo, lo strumento completo e il forte supporto comunitario lo rendono una scelta eccellente per i team che cercano di costruire applicazioni mobili di alta qualità in modo efficiente.
__HTML_TAG_81_
copia funzioneToClipboard() {}
const commands = document.querySelectorAll('code');
tutti i Comandi = '';
comandi. per ogni(cmd => AllCommands += cmd.textContent + '\n');
navigatore.clipboard.writeText(tutti iComandi);
alert('Tutti i comandi copiati a clipboard!');
#
funzione generaPDF() {
finestra.print();
#
Traduzione:
// tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"lib": ["es2017", "es7", "es6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-native"
},
"include": [
"src/**/*",
"App.tsx"
],
"exclude": [
"node_modules"
]
}
// store/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import authReducer from './authSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
auth: authReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// Usage in component
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';
const CounterScreen = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<View style={styles.container}>
<Text style={styles.countText}>Count: {count}</Text>
<Button title="+" onPress={() => dispatch(increment())} />
<Button title="-" onPress={() => dispatch(decrement())} />
<Button title="+5" onPress={() => dispatch(incrementByAmount(5))} />
</View>
);
};
// App.js with Redux Provider
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store/store';
import AppNavigator from './navigation/AppNavigator';
const App = () => {
return (
<Provider store={store}>
<AppNavigator />
</Provider>
);
};
export default App;
import React from 'react';
import styled from 'styled-components/native';
const Container = styled.View`
flex: 1;
padding: 20px;
background-color: #f5f5f5;
`;
const Title = styled.Text`
font-size: 24px;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
color: #333;
`;
const StyledButton = styled.TouchableOpacity`
background-color: ${props => props.primary ? '#007AFF' : '#FF3B30'};
padding: 15px 30px;
border-radius: 10px;
margin-bottom: 10px;
align-items: center;
`;
const ButtonText = styled.Text`
color: white;
font-size: 16px;
font-weight: 600;
`;
const StyledComponentsExample = () => {
return (
<Container>
<Title>Styled Components</Title>
<StyledButton primary onPress={() => console.log('Primary pressed')}>
<ButtonText>Primary Button</ButtonText>
</StyledButton>
<StyledButton onPress={() => console.log('Secondary pressed')}>
<ButtonText>Secondary Button</ButtonText>
</StyledButton>
</Container>
);
};
export default StyledComponentsExample;
// services/httpClient.js
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
const httpClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
httpClient.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor
httpClient.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
if (error.response?.status === 401) {
// Handle unauthorized access
await AsyncStorage.removeItem('authToken');
// Navigate to login screen
}
return Promise.reject(error);
}
);
export default httpClient;
// services/userService.js
import httpClient from './httpClient';
export const userService = {
getUsers: () => httpClient.get('/users'),
getUser: (id) => httpClient.get(`/users/${id}`),
createUser: (userData) => httpClient.post('/users', userData),
updateUser: (id, userData) => httpClient.put(`/users/${id}`, userData),
deleteUser: (id) => httpClient.delete(`/users/${id}`),
};
// utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storage = {
// Store data
setItem: async (key, value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
} catch (error) {
console.error('Error storing data:', error);
}
},
// Retrieve data
getItem: async (key) => {
try {
const jsonValue = await AsyncStorage.getItem(key);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (error) {
console.error('Error retrieving data:', error);
return null;
}
},
// Remove data
removeItem: async (key) => {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.error('Error removing data:', error);
}
},
// Clear all data
clear: async () => {
try {
await AsyncStorage.clear();
} catch (error) {
console.error('Error clearing storage:', error);
}
},
// Get all keys
getAllKeys: async () => {
try {
return await AsyncStorage.getAllKeys();
} catch (error) {
console.error('Error getting keys:', error);
return [];
}
},
};
// Usage in component
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import { storage } from '../utils/storage';
const StorageExample = () => {
const [name, setName] = useState('');
const [savedName, setSavedName] = useState('');
useEffect(() => {
loadSavedName();
}, []);
const loadSavedName = async () => {
const saved = await storage.getItem('userName');
if (saved) {
setSavedName(saved);
}
};
const saveName = async () => {
await storage.setItem('userName', name);
setSavedName(name);
setName('');
};
const clearName = async () => {
await storage.removeItem('userName');
setSavedName('');
};
return (
<View style={styles.container}>
<Text style={styles.title}>AsyncStorage Example</Text>
<TextInput
style={styles.input}
placeholder="Enter your name"
value={name}
onChangeText={setName}
/>
<Button title="Save Name" onPress={saveName} />
<Button title="Clear Name" onPress={clearName} />
{savedName ? (
<Text style={styles.savedText}>Saved Name: {savedName}</Text>
) : (
<Text style={styles.noDataText}>No saved name</Text>
)}
</View>
);
};
// __tests__/App.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import '@testing-library/jest-native/extend-expect';
import App from '../App';
describe('App', () => {
it('renders correctly', () => {
const { getByText } = render(<App />);
expect(getByText('Welcome to React Native!')).toBeTruthy();
});
it('handles button press', async () => {
const { getByText, getByTestId } = render(<App />);
const button = getByTestId('test-button');
fireEvent.press(button);
await waitFor(() => {
expect(getByText('Button pressed!')).toBeTruthy();
});
});
});
// Component testing
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import CustomButton from '../components/CustomButton';
describe('CustomButton', () => {
it('calls onPress when pressed', () => {
const mockOnPress = jest.fn();
const { getByText } = render(
<CustomButton title="Test Button" onPress={mockOnPress} />
);
fireEvent.press(getByText('Test Button'));
expect(mockOnPress).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
const { getByText } = render(
<CustomButton title="Test Button" disabled={true} />
);
const button = getByText('Test Button').parent;
expect(button).toBeDisabled();
});
});
// e2e/firstTest.e2e.js
describe('Example', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
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();
});
});
// .detoxrc.json
{
"testRunner": "jest",
"runnerConfig": "e2e/config.json",
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/YourApp.app",
"build": "xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 12"
}
},
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_4_API_30"
}
}
}
}
// utils/flipper.js
import { logger } from 'react-native-logs';
const defaultConfig = {
severity: __DEV__ ? 'debug' : 'error',
transport: __DEV__ ? logger.consoleTransport : logger.fileAsyncTransport,
transportOptions: {
colors: {
info: 'blueBright',
warn: 'yellowBright',
error: 'redBright',
},
},
};
const log = logger.createLogger(defaultConfig);
export default log;
// Usage in components
import log from '../utils/flipper';
const MyComponent = () => {
const handlePress = () => {
log.debug('Button pressed');
log.info('User interaction logged');
};
return (
<Button title="Press me" onPress={handlePress} />
);
};
// android/app/build.gradle
android {
...
signingConfigs {
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}
// android/gradle.properties
MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=my-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=*****
MYAPP_UPLOAD_KEY_PASSWORD=*****
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Build and upload to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp")
upload_to_testflight
end
desc "Build and upload to App Store"
lane :release do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp")
upload_to_app_store
end
end
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Build and upload to Play Console"
lane :beta do
gradle(task: "clean bundleRelease")
upload_to_play_store(track: 'beta')
end
desc "Deploy to Play Store"
lane :release do
gradle(task: "clean bundleRelease")
upload_to_play_store
end
end
// App.js with CodePush
import codePush from 'react-native-code-push';
const App = () => {
return (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
};
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESUME,
};
export default codePush(codePushOptions)(App);
// Release update
// code-push release-react MyApp-iOS ios
// code-push release-react MyApp-Android android