En respectant la structure vue précédemment, créer les 2 écrans suivants
Form.js
import { View, TextInput, Text, Switch, Button } from 'react-native';
export default function Form() {
return (
<View style={{ flex: 1, gap: 16, justifyContent: 'center', alignItems: 'center' }}>
<TextInput
placeholder="Name"
/>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Switch />
<Text>Dark mode?</Text>
</View>
<Button
title="Show!"
/>
</View>
);
}
Show.js
import { View, Text, Button } from 'react-native';
export default function Show() {
return (
<View style={{ flex: 1, gap: 16, justifyContent: 'center', alignItems: 'center' }}>
<Text>Show</Text>
<Button
title="Again?"
/>
</View>
);
}
En consultant la documentation des components qu'on utilise(Button, TextInput, Switch, Librairie, etc.) on constate un type de prop commençant par on...
, ex: onPress
. C'est la nomenclature standard pour identifier les événements.
On peut ensuite s'inscrire pour être informé du déclenchement de l'événement via JSX et exécuter le comportement désiré.
TextInput onChangeText
<TextInput
placeholder="Name"
onChangeText={ (text) => console.log(text) }
/>
On utilise ici la forme inline de l'inscription à l'événement. () => ...
représente la notation raccourcie des fonctions fléchées.
Button onPress
export default function Form() {
function pressed() {
console.log('pressed!');
}
return (
{/* ... */}
<Button
title="Show!"
onPress={ pressed }
/>
{/* ... */}
);
}
On peut également envoyer en référence une fonction traditionnelle.
On ne peut pas exécuter directement notre code, il faut absolument passer par une fonctions. L'événement appelera cette fonction en fournissant les arguments appropriés.
Le state permets au component de conserver un état au fil temps. Autrement, chaque fois que le component de met à jour, donc render, la fonction est appelée sans traces des variables qu'elle contenait précédemment. React offre un mécanisme pour stocker de l'information qui sera accessible d'un render à l'autre.
C'est cet état qui permets à React de suivre l'évolution du component et de constater les changements qui demande une mise à jour. Attention les données d'état sont immutables, on ne doit pas changer directement la valeur d'une variable, mais plutôt assigner une copie contenant la nouvelle valeur. C'est ce changement de référence qui indique à React de re-render.
Switch
import { useState } from 'react';
export default function Form() {
const [checked, setChecked] = useState(false);
console.log(checked);
return (
{/* ... */}
<Switch
value={ checked }
onValueChange={ (on) => setChecked(on) }
/>
{/* ... */}
);
}
Pour suivre plusieurs valeurs reliées, il est intéressant d'encapsuler l'état dans un objet.
Form.js
import { View, TextInput, Text, Switch, Button } from 'react-native';
import { useState } from 'react';
export default function Form() {
const [display, setDisplay] = useState({});
// {} VS { name: '', darkMode: false }
console.log(display);
return (
{/* ... */}
<TextInput
placeholder="Name"
value={ display.name }
onChangeText={ (text) => {
setDisplay({...display, name: text}); // Destructuring pour creer la copie
console.log(`onChangeText: ${display.name}`); // ATTENTION
}}
/>
{/* ... */}
<Switch
value={ display.darkMode }
onValueChange={ (on) => setDisplay({...display, darkMode: on}) }
/>
{/* ... */}
);
}
Attention La nouvelle valeur assignée n'est pas disponible immédiatement dans le même cycle de rendu.
Les Toasts permettent d'afficher un message qui disparait automatiquement. Ils sont natifs sur Android mais pas iOS, la librairie Root Toast permets d'uniformiser ce comportement.
Validation nom requis Form.js
import { RootSiblingParent } from 'react-native-root-siblings';
import Toast from 'react-native-root-toast';
export default function Form() {
function pressed() {
if (display.name.trim() == '') {
Toast.show('Provide a name', {
duration: Toast.durations.SHORT,
backgroundColor: 'red',
textColor: 'white',
});
}
}
return (
<RootSiblingParent>
<View style={{ flex: 1, gap: 16, justifyContent: 'center', alignItems: 'center' }}>
{/* ... */}
</View>
</RootSiblingParent>
);
}
Les alertes sont un boîte de dialogue intéractive contenant un titre, un message optionnel et jusqu'à 3 boutons.
Button Show.js
import { View, Text, Button, Alert } from 'react-native';
export default function Show() {
function againPressed() {
Alert.alert('Title', 'Message', [
{
text: 'A',
onPress: () => console.log('pressed a')
},
{
text: 'B',
onPress: () => console.log('pressed b')
},
{
text: 'C',
onPress: () => console.log('pressed c')
},
]);
}
{/* ... */}
}
npx expo install @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context
App.js
import { View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Form from './Form.js';
import Show from './Show.js';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<View style={{ flex: 1, backgroundColor: 'teal' }}>
<NavigationContainer>
<Stack.Navigator initialRouteName="Form">
<Stack.Screen
name="Form"
component={ Form }
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Show" component={ Show } />
</Stack.Navigator>
</NavigationContainer>
</View>
);
}
Pour déclencher la navigation, chaque component utilisé en tant que Screen reçoit une prop navigation. navigate(name)
permet d'aller vers une nouvelle page, ou de retourner sur une page existante. Par défaut, si on est déjà sur la page, la navigation n'est pas redéclenchée.
Form.js
export default function Form({ navigation }) {
function pressed() {
if (display.name.trim() == '') {
// ...
} else {
navigation.navigate('Show');
}
}
// ...
}
On peut fournir des paramètre lors de la navigation.
Form.js
navigation.navigate('Show', display);
Show.js
import { View, Text, Button, Alert } from 'react-native';
export default function Show({ route }) {
console.log(`Show ${JSON.stringify(route.params)}`);
const display = route.params;
return (
<View
style={{
flex: 1, gap: 16, justifyContent: 'center', alignItems: 'center',
backgroundColor: (display.darkMode ? 'black' : 'white')
}}
>
<Text style={{ color: (display.darkMode ? 'white' : 'black') }}>
{ display.name }
</Text>
{/* ... */}
</View>
);
}
Plusieurs options de configuration disponibles, soit via le NavigationContainer ou directement dans les écrans enfants
App.js
import { StatusBar } from 'expo-status-bar';
import { View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Form from './Form.js';
import Show from './Show.js';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<>
<StatusBar style="light" />
<View style={{ flex: 1, backgroundColor: 'red' }}>
{/* View wrapper si backgroundColor dans les components enfants */}
<NavigationContainer>
<Stack.Navigator
initialRouteName="Form"
screenOptions={{
headerStyle: {
backgroundColor: 'seagreen'
},
headerTintColor: 'white',
}}
>
<Stack.Screen
name="Form"
component={ Form }
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Show" component={ Show } />
</Stack.Navigator>
</NavigationContainer>
</View>
</>
);
}
Show.js
import { StatusBar } from 'expo-status-bar';
import { View, Text, Button, Alert } from 'react-native';
export default function Show({ navigation, route }) {
console.log(`Show ${JSON.stringify(route.params)}`);
const display = route.params;
navigation.setOptions({
headerTintColor: display.darkMode ? 'black' : 'white',
});
function againPressed() {
Alert.alert('Title', 'Message', [
{
text: 'Pop(to root)',
onPress: () => console.log('pressed a')
},
{
text: 'Nav(back)',
onPress: () => console.log('pressed b')
},
{
text: 'Push(new)',
onPress: () => console.log('pressed c')
},
]);
}
return (
<>
<StatusBar style={ display.darkMode ? 'dark' : 'light' } />
<View
style={{
flex: 1, gap: 16, justifyContent: 'center', alignItems: 'center',
backgroundColor: (display.darkMode ? 'black' : 'white')
}}
>
<Text style={{ color: (display.darkMode ? 'white' : 'black') }}>
{ display.name }
</Text>
<Button
title="Again?"
onPress={ againPressed }
/>
</View>
</>
);
}
Il existe 4 principaux modes de navigation pour les Stack
Show.js
export default function Show({ navigation, route }) {
{/* ... */}
function againPressed() {
Alert.alert('Title', 'Message', [
{
text: 'Pop(root)',
onPress: () => navigation.popToTop()
},
{
text: 'Nav(reuse)',
onPress: () => navigation.navigate('Form')
},
{
text: 'Push(new)',
onPress: () => navigation.push('Form')
},
]);
}
{/* ... */}
}
Lorsqu'on retourne sur un écran, on peut recevoir des données de navigation
Show.js
navigation.navigate('Form', { name: '' })
Form.js
import { useState, useEffect } from 'react';
export default function Form({ navigation, route }) {
{/* ... */}
console.log(route.params);
useEffect(() => {
console.log(`effect ${JSON.stringify(route.params)}`);
if (route.params) {
setDisplay({ ...display, name: route.params.name });
}
}, [route.params]);
{/* ... */}
}
useEffect est un mécanisme de React permettant de réagir au changement d'un état, state, en vue de synchroniser avec un élément externe: requête HTTP, timeout/interval, NavigationContainer, etc.
Le second paramètre de la fonction est les dépendances déclenchant l'effet
Show.js
import { useEffect } from 'react';
export default function Show({ navigation, route }) {
{/* ... */}
useEffect(() => {
navigation.setOptions({
headerTintColor: display.darkMode ? 'black' : 'white',
});
}, [])
{/* ... */}
}