E chegamos à segunda parte da nossa série onde vou lhe ensinar como construir um app Android e iOS que faz um CRUD completo (cadastro, listagem, edição e exclusão de dados) usando React Native.
Na primeira parte, criamos o projeto, estruturamos a navegação em abas e criamos apenas a aparência do formulário de cadastro. Nosso app de lista de compras do supermercado está apenas começando a ser construído!
Atenção: esta é uma série intermediária. Caso nunca tenha feito um app em React Native antes, sugiro começar por esta outra série.
#4 – Estados do formulário
Agora vamos programar os estados dos campos, para que seus valores sejam armazenados em memória quando o usuário digitar neles.
Primeiro, vamos precisar importar a função useState do React , isso no AppForm.js.
1 2 3 |
import React, {useState} from 'react'; |
Depois, vamos criar nossos objetos de estado e as funções de manipulação do estado, dentro da function AppForm e antes do seu retorno.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
export default function AppForm({ navigation }) { const [descricao, setDescricao] = useState(''); const [quantidade, setQuantidade] = useState(''); function handleDescriptionChange(descricao){ setDescricao(descricao); } function handleQuantityChange(quantidade){ setQuantidade(quantidade); } function handleButtonPress(){ console.log({id: new Date().getTime(), descricao, quantidade}); navigation.navigate("AppList"); } |
Note que também deixei preparada uma function para manipular o ‘press’ do botão, que por enquanto apenas imprime no console um objeto JSON com os dados obtidos e um ID baseado no número de milissegundos desde 01/01/1970. Este é um jeito bem bobo de gerar ids únicos mas que vai funcionar para um banco local sem concorrência, como é o caso deste app.
Essa função também joga o usuário de volta para a aba de listagem, o que será muito útil mais tarde. Note que ele faz isso usando um objeto navigation, que estou recebendo por parâmetro na function default.
Agora, vamos referenciar esta funções de manipulação de estado nas propriedades onChangeText dos inputs e onPress do botão, como abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<TextInput style={styles.input} onChangeText={handleDescriptionChange} placeholder="O que está faltando em casa?" clearButtonMode="always" /> <TextInput style={styles.input} onChangeText={handleQuantityChange} placeholder="Digite a quantidade" keyboardType={'numeric'} clearButtonMode="always" /> <TouchableOpacity style={styles.button} onPress={handleButtonPress}> <Text style={styles.buttonText}>Salvar</Text> </TouchableOpacity> |
Agora, ao atualizar a nossa aplicação React Native, preencher os campos do formulário e clicar no botão de Salvar, o console deve exibir o objeto JSON dos campos mapeados e nos jogar de volta para a aba de listagem.
Uma dica é testar tanto no smartphone quanto no simulador/emulador.
Isso mostra que tudo que fizemos até o momento está funcionando.
#5 – Salvando no banco de dados
Agora que temos todo o formulário pronto e funcionando, falta só pegarmos os dados capturados pelos estados e salvar no banco de dados local do dispositivo.
Como estamos usando o Expo, usaremos uma lib chamada Expo Async Storage para este finalidade. Nós instalaremos esta lib usando o comando abaixo, na pasta do seu projeto.
1 2 3 |
expo install @react-native-async-storage/async-storage |
Este AsyncStorage é uma forma bem simples para armazenar dados locais baseado em chave-valor, onde o valor é sempre textual. Sendo sempre textual, o mais comum é a gente salvar objetos JSON em forma de texto, o que chamamos geralmente de objeto “serializado”.
Para usá-lo, vamos alterar os nossos imports do AppForm.js.
1 2 3 4 5 6 |
import React, {useState} from 'react'; import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Text, View, TextInput, TouchableOpacity } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; |
E agora, na nossa função handleButtonPress que hoje só imprime o objeto no console, vamos usá-la para salvar os dados obtidos no nosso banco de dados.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
async function handleButtonPress(){ const listItem = {id: new Date().getTime(), descricao, quantidade: parseInt(quantidade)}; let savedItems = []; const response = await AsyncStorage.getItem('items'); if(response) savedItems = JSON.parse(response); savedItems.push(listItem); await AsyncStorage.setItem('items', JSON.stringify(savedItems)); navigation.navigate("AppList", listItem); } |
Note que para conseguir salvar, primeiro retornamos o valor do elemento ‘items’ que podemos entender como se fosse um array de elementos JSON em forma de texto, tipo um primo pobre do MongoDB. Esse retorno deve ser convertido para JSON (savedItems).
Em seguida, adicionamos o novo item neste mesmo array e enviamos ele de volta, em forma de texto, para o AsyncStorage poder finalizar o trabalho. Note também que tive de colocar um async antes da function, para que o await funcione, pois as chamadas do AsyncStorage são assíncronas.
Agora você pode testar, cadastrando uma série de itens, mas somente vai ver alguma coisa acontecendo no console, pois a tela de listagem ainda não funciona, o que faremos na sequência.
E com isso, finalizamos a letra C do nosso CRUD, o Create!
#6 – Criando a listagem
Uma vez que já temos o cadastro de itens da nossa lista de compras funcionando, podemos construir a outra tela, de listagem. Criar ela será um pouco mais complexo que a de formulário, pois ela é uma listagem de itens e, quando temos uma listagem de itens, temos de criar um componente para o item primeiro.
Vamos chamá-lo de AppItem.js, com o conteúdo que mostrarei a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React from 'react'; import {StyleSheet, Text, View, TouchableOpacity} from 'react-native'; export default function AppItem(props){ return ( <View style={styles.container}> <Text style={styles.textItem}>{props.item}</Text> <View style={styles.buttonsContainer}> <TouchableOpacity style={styles.deleteButton} > <Text style={styles.buttonText}>X</Text> </TouchableOpacity> <TouchableOpacity style={styles.editButton} > <Text style={styles.buttonText}>Editar</Text> </TouchableOpacity> </View> </View> ); } |
Os imports são auto-explicativos, mas a function default requer atenção. Primeiro, ela irá receber um objeto props que conterá o texto do item a ser adicionado na listagem. Além do texto, temos dois botões, um de editar o item e outro de excluir.
Eu tive de adicionar uma view entre esses dois botões para poder estilizá-los de maneira diferente do restante do item, como verá a seguir, principalmente se tratando do flexDirection.
Abaixo, o estilo deste mesmo componente AppItem.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
const styles = StyleSheet.create({ container: { backgroundColor: '#fff', marginTop: 20, width: '100%' }, buttonsContainer: { flexDirection: 'row-reverse', alignItems: 'flex-end', borderBottomWidth: 1, borderBottomColor: '#CCC', paddingBottom: 10, marginTop: 10, }, editButton: { marginLeft: 10, height: 40, backgroundColor: 'blue', borderRadius: 10, padding: 10, fontSize: 12, elevation: 10, shadowOpacity: 10, shadowColor: '#ccc', alignItems: 'center' }, deleteButton: { marginLeft: 10, height: 40, width: 40, backgroundColor: 'red', borderRadius: 10, padding: 10, fontSize: 12, elevation: 10, shadowOpacity: 10, shadowColor: '#ccc', alignItems: 'center' }, buttonText: { color: '#fff', fontWeight: 'bold', }, textItem: { fontSize: 20, } }); |
Eu estilizei individualmente o container geral, o container dos botões, o botão de edição, o botão de exclusão, um estilo geral para o texto dos mesmos e mais um estilo para o texto do item em si.
Opcionalmente você pode dar uma olhada em pacotes de ícones na Internet para os botões usarem ícones ao invés de texto. Como não quero me alongar muito em frontend, que é algo bem custoso, vamos com texto mesmo.
Agora que temos o nosso item da lista, vamos construir a lista em si, representada no módulo AppList.js.
Primeiro, vamos revisar os imports deste arquivo, adicionando alguns itens novos que usaremos a seguir.
1 2 3 4 5 6 |
import { StatusBar } from 'expo-status-bar'; import React, {useState} from 'react'; import { StyleSheet, View, Text, ScrollView } from 'react-native'; import AppItem from './AppItem'; |
Segundo, vamos adicionar na função default desta tela um estado para o array de itens que, em um primeiro momento, estarão estáticos, mas que futuramente virão do banco de dados.
1 2 3 4 5 6 7 8 9 10 |
const [items, setItems] = useState([ {id: 1, quantidade: 5, descricao: "arroz" }, {id: 2, quantidade: 1, descricao: "feijão" }, {id: 3, quantidade: 0.5, descricao: "lentilha" }, {id: 4, quantidade: 1, descricao: "massa" }, {id: 5, quantidade: 1, descricao: "katchup" }, {id: 6, quantidade: 1, descricao: "queijo-ralado" } ]); |
Com esse estado pronto, vamos mudar a interface desta tela para que tenha uma ScrollView (área com rolagem) e dentro dela, vamos executar um código JavaScript que vai adicionar itens dinamicamente conforme o conteúdo de um array que definimos um pouco antes do return com conteúdos estáticos, apenas para podermos visualizar antes de tornar esta página realmente dinâmica.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
return ( <View style={styles.container}> <StatusBar style="light" /> <Text style={styles.title}>Lista de Compras</Text> <ScrollView style={styles.scrollContainer} contentContainerStyle={styles.itemsContainer}> { items.map(item => { return <AppItem key={item.id} id={item.id} item={item.quantidade + ' de ' + item.descricao} /> }) } </ScrollView> </View> ); |
Aqui, vale uma menção ao ScrollView que além da propriedade style, que é geral, tem a contentContainerStyle, referente ao conteúdo no seu interior.
Note também como usei o nosso componente AppItem, passando propriedades pra ele, oriundos dos itens do array armazenado no estado que criamos antes. A propriedade key é obrigatória e usaremos ela mais tarde, na listagem e exclusão.
Você vai precisar de alguns estilos novos também, que pode conferir abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#D93600', alignItems: 'center', justifyContent: 'center' }, title: { color: '#fff', fontSize: 20, fontWeight: 'bold', marginTop: 50, marginBottom: 20 }, scrollContainer: { flex: 1, width: '90%' }, itemsContainer: { flex: 1, marginTop: 10, padding: 20, borderTopLeftRadius: 10, borderTopRightRadius: 10, alignItems: 'stretch', backgroundColor: '#fff' }, }); |
Aqui não tem absolutamente nenhuma novidade em relação ao que já fizemos antes.
Como resultado, você deve ter uma tela de listagem com a seguinte aparência.
E com isso, encerramos a segunda parte do nosso tutorial.
Na terceira parte, vamos tornar esta listagem dinâmica e partir para as demais letras do CRUD. Confira neste link.
Um abraço e sucesso.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.