ReactJS
ReactNative
npx create-expo-app demo --template blank
cd demo
npx expo install react-native-safe-area-context react-native-root-toast
npx expo start --tunnel
├── App.js # Point d'entrée du code
├── app.json # Metadonnées du projet: nom, version, configurations...
├── assets # Ressources statiques: images, sons, pdf, etc.
│ ├── icon.png
│ └── splash.png
├── babel.config.js # Configuration de build
├── .expo # Fichiers locaux de l'environnement de build Expo
├── .gitignore
├── package.json # Liste des dépendances JS
└── package-lock.json
import { StatusBar } from 'expo-status-bar';
import { RootSiblingParent } from 'react-native-root-siblings';
import Toast from 'react-native-root-toast';
import { useState } from 'react';
import { StyleSheet, View, ScrollView, Text, TextInput, Switch, Button, TouchableHighlight, Alert } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
const EMPTY_TODO = () => { return {
name: null,
done: false
}}
const SEED = [...Array(10).keys()].map((item, index, array) => {
const name = `Todo ${item}`
return {
id: Math.random().toString(16).substring(2),
name: name,
description: `Description ${name} `.repeat(index),
done: (index % 3)
}
})
export default function App() {
const [todos, setTodos] = useState(SEED);
const [newTodo, setNewTodo] = useState(EMPTY_TODO());
function handleAdd() {
if ((newTodo.name?.trim() ?? '') == '') {
Toast.show(
'Provide a Todo name',
{
duration: Toast.durations.SHORT,
backgroundColor: 'red',
textColor: 'white',
}
);
} else {
newTodo.id = Math.random().toString(16).substring(2);
setTodos([newTodo, ...todos]);
setNewTodo(EMPTY_TODO());
}
}
function toggle(id) {
setTodos(
todos.map( t => {
if (t.id == id) {
t.done = !t.done
}
return t;
})
)
}
function handleToggle(todo) {
Alert.alert(null, todo.name, [
{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{
text: todo.done ? 'Incomplete' : 'Completed',
onPress: () => toggle(todo.id)
},
]);
}
return (
<RootSiblingParent>
<StatusBar style="auto" />
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Text style={{ fontSize: 28, fontWeight: 'bold', textAlign: 'center' }}>Todoer</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TextInput
placeholder='New task'
style={[styles.input, { height: 48, flexGrow: 1 }]}
value={ newTodo.name }
onChangeText={ (text) => setNewTodo({...newTodo, name: text}) }
/>
<Switch
value={ newTodo.done }
onValueChange={ (checked) => setNewTodo({...newTodo, done: checked}) }
/>
</View>
<TextInput
placeholder='Optional description'
style={[styles.input, { width: '100%', height: 96, verticalAlign: 'top' }]}
multiline={ true }
/>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* Envelopper dans une View pour eviter une largeur de 100% */}
<Button
title='Add'
color='green'
onPress={ handleAdd }
/>
</View>
{/* Pas la facon ideale pour afficher une liste, simplification pour la demonstration */}
<ScrollView style={{ flexGrow: 1 }}>
{
todos.map((todo, index, array) =>
<TouchableHighlight key={todo.id} onPress={ () => { handleToggle(todo) } }>
<View>
<View
style={{
flexDirection: 'row',
columnGap: 8,
paddingVertical: 16,
paddingHorizontal: 8,
backgroundColor: 'white', /* requis pour highlight */
}}
>
<Ionicons name="checkmark-circle" size={22} color={ todo.done ? 'green' : 'lightgray' } />
<View style={{ flex: 1, flexDirection: 'column' }}>
<Text style={[styles.text, { fontWeight: 'bold' }]}>{ todo.name }</Text>
{
todo.description &&
<Text style={ styles.text }>{ todo.description }</Text>
}
</View>
</View>
{
(index != array.length - 1) &&
<View style={{ height: 1, backgroundColor: 'lightgray' }} />
}
</View>
</TouchableHighlight>
)
}
</ScrollView>
</SafeAreaView>
</SafeAreaProvider>
</RootSiblingParent>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 16,
justifyContent: 'center',
padding: 16,
},
input: {
borderWidth: 1,
borderColor: 'lightgray',
padding: 8,
},
text: {
fontSize: 18,
},
});
<Text style={{ fontSize: 28, fontWeight: 'bold', textAlign: 'center' }}>Todoer</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TextInput
placeholder='New task'
style={[styles.input, { height: 48, flexGrow: 1 }]}
value={ newTodo.name }
onChangeText={ (text) => setNewTodo({...newTodo, name: text}) }
/>
<Switch
value={ newTodo.done }
onValueChange={ (checked) => setNewTodo({...newTodo, done: checked}) }
/>
</View>
<TextInput
placeholder='Optional description'
style={[styles.input, { width: '100%', height: 96, verticalAlign: 'top' }]}
multiline={ true }
/>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* Envelopper dans une View pour eviter une largeur de 100% */}
<Button
title='Add'
color='green'
onPress={ handleAdd }
/>
</View>
import { useState } from 'react';
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState({ name: null, done: false });
// ...
<Input
placeholder='New task'
style={{ height: 48, flexGrow: 1 }}
value={ newTodo.name }
onChangeText={ (text) => setNewTodo({...newTodo, name: text}) }
/>
<Text>{newTodo.name}</Text>
import { StyleSheet, TextInput } from 'react-native';
export default function Input({style, ...otherProps}) {
return (
<TextInput
style={[styles.input, style]}
{...otherProps}
/>
)
}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: 'lightgray',
padding: 8,
},
});
import { Alert, View, Text, TouchableHighlight } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
export default function TodoItem({ todo, toggle }) {
function handleToggle(todo) {
Alert.alert(null, todo.name, [
{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{
text: todo.done ? 'Incomplete' : 'Completed',
onPress: () => toggle(todo.id)
},
]);
}
return (
<TouchableHighlight key={todo.id} onPress={ () => { handleToggle(todo) } }>
<View>
<View
style={{
flexDirection: 'row',
columnGap: 8,
paddingVertical: 16,
paddingHorizontal: 8,
backgroundColor: 'white', /* requis pour highlight */
}}
>
<Ionicons name="checkmark-circle" size={22} color={ todo.done ? 'green' : 'lightgray' } />
<View style={{ flex: 1, flexDirection: 'column' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>{ todo.name }</Text>
{
todo.description &&
<Text style={{ fontSize: 18 }}>{ todo.description }</Text>
}
</View>
</View>
</View>
</TouchableHighlight>
)
}
import { StatusBar } from 'expo-status-bar';
import { RootSiblingParent } from 'react-native-root-siblings';
import Toast from 'react-native-root-toast';
import { useState } from 'react';
import { StyleSheet, View, ScrollView, Text, TextInput, Switch, Button, TouchableHighlight, Alert } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import Input from './Input';
import TodoItem from './TodoItem';
const EMPTY_TODO = () => { return {
name: null,
done: false
}}
const SEED = [...Array(10).keys()].map((item, index, array) => {
const name = `Todo ${item}`
return {
id: Math.random().toString(16).substring(2),
name: name,
description: `Description ${name} `.repeat(index),
done: (index % 3)
}
})
export default function App() {
const [todos, setTodos] = useState(SEED);
const [newTodo, setNewTodo] = useState(EMPTY_TODO());
function handleAdd() {
if ((newTodo.name?.trim() ?? '') == '') {
Toast.show('Provide a Todo name', {
duration: Toast.durations.SHORT,
backgroundColor: 'red',
textColor: 'white',
});
} else {
newTodo.id = Math.random().toString(16).substring(2);
setTodos([newTodo, ...todos]);
setNewTodo(EMPTY_TODO());
}
}
function toggle(id) {
setTodos(
todos.map( t => {
if (t.id == id) {
t.done = !t.done
}
return t;
})
)
}
function list() {
return (
<ScrollView style={{ flexGrow: 1 }}>
{
/* Pas la facon ideale pour afficher une liste, simplification pour la demonstration */
todos.map((todo, index, array) => {
return (
<View key={ todo.id }>
<TodoItem todo={ todo } toggle={ toggle } />
{
/* Divider */
(index != array.length - 1) &&
<View style={{ height: 1, backgroundColor: 'lightgray' }} />
}
</View>
)
})
}
</ScrollView>
)
}
return (
<RootSiblingParent>
<StatusBar style="auto" />
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Text style={{ fontSize: 28, fontWeight: 'bold', textAlign: 'center' }}>Todoer</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<Input
placeholder='New task'
style={{ height: 48, flexGrow: 1 }}
value={ newTodo.name }
onChangeText={ (text) => setNewTodo({...newTodo, name: text}) }
/>
<Switch
value={ newTodo.done }
onValueChange={ (checked) => setNewTodo({...newTodo, done: checked}) }
/>
</View>
<Input
placeholder='Optional description'
style={{ width: '100%', height: 96, verticalAlign: 'top' }}
multiline={ true }
/>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* Envelopper dans une View pour eviter une largeur de 100% */}
<Button
title='Add'
color='green'
onPress={ handleAdd }
/>
</View>
{ list() }
</SafeAreaView>
</SafeAreaProvider>
</RootSiblingParent>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 16,
justifyContent: 'center',
padding: 16,
},
text: {
fontSize: 18,
},
});
Pour utiliser le debugger via l'environnement distant de la machine virtuelle, il faut récupérer l'URL des Devtools manuellement
cd demo
sed -i '70 i console.log(url);' node_modules/@expo/cli/build/src/start/server/middleware/inspector/JsInspector.js
Puis,
Le debugger fonctionne uniquement avec un navigateur basé sur Chrome, avec les configurations suivantes: