Notre store Redux en place, attardons-nous maintenant sur le formulaire de création d’une bière dans notre application React Native. Pour cela, nous allons utiliser redux-form
.
Pour suivre cet article, il est conseillé de checkout la branche 3-forms-with-redux-form du repo de l’application React Native.
Usage de redux-form dans la gestion de formulaires React
La documentation de redux-form est disponible sur ce lien et permet de comprendre rapidement la logique de cette librairie.

Motivations
Dans une application React, le développement de formulaires peut s’avérer une tâche fastidieuse et, surtout, répétitive. Validation des formulaires, affichage des erreurs, gestion des événements sur chaque champ, etc. Tout ça se voit fortement simplifié par l’usage de librairies comme Final Form, Formik ou Redux Form.
Logique générale
L’intégration de redux-form
dans une application mobile React Native se fait en trois étapes importantes :
- d’une part, l’enregistrement du reducer fourni par
redux-form
dans notre store Redux - ensuite, l’usage de composants
<Field />
ou<FieldArray />
(mis à disposition parredux-form
) pour créer les champs dans nos composants de formulaire - et enfin, la connexion de nos composants de formulaire au reducer créé à l’étape 1, via le composant d’ordre supérieur (HOC)
reduxForm()
Vous cherchez un développeur React Native freelance ?
Je vous accompagne dans la conception de votre application mobile iOS et Android.
Implémentation technique de Redux Form
Ajout du reducer formReducer
La première étape est donc d’importer le reducer de Redux Form dans notre store. Dans ses coulisses, il enregistre les données d’un formulaire en cours de remplissage dans un objet JS. On y trouvera aussi des informations sur ce formulaire : est-il valide, a-t-il des erreurs, une interaction a-t-elle été faite dessus, quel est le champ actif, quels sont les valeurs des champs, etc.

On va donc éditer configureStore.js pour intégrer ce reducer fourni par Redux Form.
import { reducer as formReducer } from "redux-form";
const persistConfig = {
key: "brkp",
storage: FilesystemStorage,
blacklist: ["form"],
};
const rootReducer = combineReducers({
form: formReducer,
beers: beersReducer,
});
On profite donc de la fonction combineReducers()
de Redux pour désormais cumuler deux reducers dans notre state d’application mobile : un pour stocker nos bières, l’autre pour Redux Form.
On note au passage, ligne 6, qu’on indique à redux-persist
d’ignorer le reducer de Redux Form en l’ajoutant à sa liste noire. Nous n’avons aucun intérêt à enregistrer dans la mémoire du téléphone l’état des formulaires de notre application, au risque de retrouver nos champs de formulaire remplis de leurs anciennes valeurs à chaque réouverture de l’appli.
Création d’un composant de champ de formulaire de type texte
Concept général d’un champ <Field />
de Redux Form
Nous allons utiliser , nous allons créer ce composant TextInput
en charge d’afficher un champ de type texte.
Redux Form va automatiquement quelques props « magiques » à notre composant <TextInput />
. On aura accès à 2 props principales :
this.props.input
pour, entre autre, exécuter des événements relatifs à un champ (blur, change, focus) ou accéder à des informations du champ (checked, name, value).this.props.meta
pour récupérer des métadonnées propres à ce champ (s’il est actif, s’il possède une erreur ou s’il est valide, s’il est « pristine » (s’il n’a jamais été touché), sa valeur initial, etc.)
Structure de base du <TextInput />
class TextInput extends Component {
render() {
const { input, ...inputProps } = this.props;
return (
<Input
{...inputProps}
onChangeText={input.onChange}
onBlur={input.onBlur}
onFocus={input.onFocus}
value={input.value.toString()}
/>
);
}
}
Dans sa version simple, notre composant de champ texte n’a besoin que de la prop input
rendue disponible par Redux Form, pour :
- déléguer les changements de valeurs du champ avec
onChangeText={input.onChange}
- déléguer les pertes de focus avec
onBlur={input.onBlur}
- déléguer les mises en focus avec
onFocus={input.onFocus}
- lui attribuer sa valeur via
value={input.value.toString()}
Les événements de ce champ sont alors sous-traités à Redux Form qui se charge de sa magie pour mettre à jour le store, évaluer la valeur du champ pour le considérer comme valide ou non, et bien plus.
Structure avancée du champ de texte
Dans sa version complète, notre <TextInput />
se retrouve entouré d’un nouveau composant <FieldWrapper />
.
class FieldWrapper extends Component {
render() {
return (
<View>
{this.props.children}
{this.props.meta.touched && this.props.meta.error && (
<Text style={styles.error}>{this.props.meta.error}</Text>
)}
{this.props.meta.warning && (
<Text style={styles.warning}>
{this.props.meta.warning}
</Text>
)}
</View>
);
}
}
C’est un conteneur qui servira à tous nos éventuels futurs autres types de champs (menu déroulant, champ photo, case à cocher, etc.) et nous évitera de répéter des logiques communes.
En l’occurrence, on affiche un message d’erreur si le champ a été touché (meta.touched
) et s’il a une erreur (meta.error
), ou un message de conseil (warning) si existant. Ces messages sont affichés sous le champ (matérialisé par {this.props.children}
).
Création du formulaire d’identification dans l’application mobile
Chaque formulaire sera encapsulé dans un composant de type classe à part entière et relié à la magie de Redux Form via le HOC reduxForm()
, à la manière du connect()
de Redux. Ces composants formulaires pourront alors être intégrés dans les écrans de notre application mobile.
Création du formulaire de login
On peut alors utiliser notre composant de champ texte pour créer notre formulaire qui servira à l’identification utilisateur.
import { reduxForm, Field } from "redux-form";
class LoginForm extends Component {
render() {
return (
<View>
<Field
name="login"
label="Identifiant"
textContentType="username"
autoCorrect={false}
autoCapitalize="none"
component={TextInput}
icon="person"
/>
<Field
name="password"
label="Mot de passe"
textContentType="password"
secureTextEntry={true}
autoCorrect={false}
autoCapitalize="none"
component={TextInput}
icon="key"
/>
<Button full warning rounded onPress={this.props.handleSubmit}>
<Text>Log in</Text>
</Button>
</View>
);
}
}
export default reduxForm({
form: "login",
})(LoginForm);
Comme mentionné précédemment, nos champs <Field />
ont tous besoin au moins d’une prop name
et component
. Les autres props sont propres à l’<Input />
utilisé issu de NativeBase (pour définir un label, le type de contenu du champ, l’auto-correction ou auto-majuscule, et l’icône).
Avec nos champs déclarés, il manque seulement un bouton qui déclenchera l’action this.props.handleSubmit()
fournie par Redux Form sur notre composant de formulaire.
Ce composant de formulaire est ensuite connecté à Redux Form et le store Redux grâce au composant d’ordre supérieur reduxForm()
. Seule option obligatoire : form
indiquant la clé représentant l’objet du formulaire dans le store.
Traitement des données du formulaire envoyé
Notre composant de formulaire <LoginForm />
étant enregistré, on peut l’utiliser dans notre écran d’identification. La seule nécessité requise par Redux Form est de lui fournir une prop onSubmit
qui s’exécutera lorsque le formulaire sera envoyé (au clic sur le bouton « Log In ») — seulement si Redux Form l’aura considéré comme valide.
class Login extends Component {
handleLoginFormSubmit = values => {
console.log(values);
// For now, fake Login Success and navigate to BeersList.
this.props.navigation.navigate("BeersList");
};
render() {
return (
<View>
<LoginForm onSubmit={this.handleLoginFormSubmit} />
</View>
);
}
}
Création du formulaire d’ajout de nouvelle bière
Création du formulaire d’ajout de bière
class BeerAddForm extends Component {
render() {
return (
<View>
<Field name="name" label="Beer name" autoCorrect={false} component={TextInput} />
<Field name="brewery" label="Brewery" autoCorrect={false} component={TextInput} />
<Field name="style" label="Style" autoCorrect={false} component={TextInput} />
<Field name="abv" label="ABV (%)" autoCorrect={false} component={TextInput} />
<Field name="aromas" label="Aromas" autoCorrect={false} component={TextInput} />
<Field name="comment" label="Comment" autoCorrect={true} component={TextInput} />
<Field name="rating" label="Rating" autoCorrect={false} component={TextInput} />
<Button full warning rounded onPress={this.props.handleSubmit}>
<Text>Add beer</Text>
</Button>
</View>
);
}
}
export default reduxForm({
form: "beerAdd",
})(BeerAddForm);
La logique est sensiblement la même que le formulaire de login. Ici, notre formulaire d’ajout de bière présente seulement un peu plus de champs textes.
Relation du formulaire envoyé avec l’action Redux addBeer()
class BeerAdd extends Component {
handleBeerAddFormSubmit = values => {
UUIDGenerator.getRandomUUID().then(uid => {
// First, add the beer.
this.props.addBeer({
uid: uid,
createdAt: Date.now(),
editedAt: null,
deletedAt: null,
photo: dummyBeerImage,
...values,
});
// Then, redirect back to BeersList.
this.props.navigation.navigate("BeersList");
});
};
render() {
return (
<View>
<BeerAddForm onSubmit={this.handleBeerAddFormSubmit} />
</View>
);
}
}
const mapDispatchToProps = dispatch => {
return {
addBeer: data => dispatch(addBeer(data)),
};
};
export default connect(
null,
mapDispatchToProps
)(BeerAdd);
Même logique pour l’intégration du formulaire sur notre écran dédié à la création d’une nouvelle bière.
Cette fois par contre, notre écran est lui-même directement relié au store Redux pour pouvoir dispatch une action addBeer()
. Cette action est exécutée dans une méthode handleBeerAddFormSubmit()
à la soumission du formulaire <BeerAddForm />
précédemment créé.
En plus des valeurs du formulaires fournies par Redux Form via le paramètre values
, on va y ajouter quelques autres valeurs extras utiles à l’identification et datation de notre bière :
- un identifiant unique
uid
généré par la librairiereact-native-uuid-generator
, pour assigner à la bière un identifiant unique (UUID de version 4) et pouvoir la retrouver plus tard pour l’éditer ou la supprimer - un timestamp
createdAt
pour stocker sa date de création - deux autres timestamps
editedAt
etdeletedAt
pour l’instant vides, mais qui risqueront d’être remplis plus tard - et une clé
photo
contenant pour l’instant un objet image factice. Sa valeur pourra à l’avenir provenir d’un champ de type photo.
Une fois la création de cette bière bien envoyé à Redux via this.props.addBeer()
, on redirige l’utilisateur vers l’écran listant ses bières avec this.props.navigation.navigate("BeersList")
.
Conclusion
Nous avons ainsi développé deux formulaires dans notre application React Native (login et création de bière). L’un est pour l’instant factice tandis que l’autre enregistre bien des données dans le store Redux, mais nous analyserons l’authentification utilisateur via AJAX entre React Native & WordPress dans un futur article.
Dans l’épisode suivant, nous améliorerons nos formulaires pour intégrer des validations, conseils et formatage automatique des valeurs.
1 commentaire
[…] Créer des formulaires dans une app React Native avec redux-form […]