Projeto de lista de compras com React Native e Expo.
git clone https://github.com/RafaelR4mos/challenge-react-native-shop-list.git package.jsonnpm installnpm run startUsar a biblioteca de ícones para a versão do RN.
npm install phosphor-react-nativeUso dos ícones:
import { Basket } from 'phosphor-react-native'
export function Componente() {
return (
<Basket
size={32}
color="#FFFFFF"
weight='bold'
/>
)
}
Componentes estilizados e utilização de temas. A maior diferença para a versão WEB fica na tipagem e apontamento para a versão RN
//styles.ts
import 'styled-components/native';
export const Container = styled.View`
color: red;
`;//tipagem
import 'styled-components/native';
import theme from '../theme'; //caminho do tema
//importante usar o '/native' aqui também.
declare module 'styled-components/native' {
type ThemeType = typeof theme;
export interface DefaultTheme extends ThemeType {}
}attrsPara que seja possível consumir o tema da aplicação e não precisar estilizar no próprio arquivo .tsx podemos usar no styles.ts o seguinte código:
//Aqui mudamos os atributos 'size' e 'color' através do arquivo de estilização
export const BackIcon = styled(CaretLeft).attrs(({ theme }) => ({
size: 32,
color: theme.COLORS.WHITE,
}))``;type no TypescriptCaso criarmos um componente personalizado e também seja necessário extender a tipagem do componente nativo, podemos utilizar como base o código:
//Importa tipagem do touchable opacity que vem do RN
import { TouchableOpacityProps } from 'react-native';
//Com o caractere '&' adiciona os tipos nativos + o que for definido entre chaves
type GroupCardProps = TouchableOpacityProps & {
title: string;
};Quando há o uso de muitas variáveis como theme, type, variant e outras em um componente só pode ser que o css helper do styled-components contribua em termos de simplificar a sintaxe.
import styled, { css } from 'styled-components/native';
//Com isso `theme` não precisa ser desestruturado em todas propriedades.
export const NumbersOfPlayers = styled.Text`
${({ theme }) => css`
color: ${theme.COLORS.GRAY_200};
font-family: ${theme.FONT_FAMILY.BOLD};
font-size: ${theme.FONT_SIZE.SM}px;
`};
`;npm install @react-navigation/nativeexponpx expo install react-native-screens react-native-safe-area-contextnpm install @react-navigation/native-stackcontexto das rotas e rotas:routes/app.routes.tsx
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { MyLists } from '../screens/MyyList';
const { Navigator, Screen } = createNativeStackNavigator();
//Navigator --> Envolve as rotas
//Screen --> Rota individual com nome e apontando para componente
export function AppRoutes() {
return (
<Navigator screenOptions={{ headerShown: false }}>
<Screen
name="myLists"
component={MyLists}
/>
</Navigator>
);
}index.tsx
import { NavigationContainer } from '@react-navigation/native';
import { useTheme } from 'styled-components/native';
import { AppRoutes } from './app.routes';
import { View } from 'react-native';
export function Routes() {
const { COLORS } = useTheme();
//NavigationContainer --> Fornece o contexto de navegação para o app.
// * A estilização da view remove o glitch effect ao trocar de página.
return (
<View style={{ flex: 1, backgroundColor: COLORS.GRAY_600 }}>
<NavigationContainer>
<AppRoutes />
</NavigationContainer>
</View>
);
}@types/É interessante tipar quais rotas existem na nossa aplicação e principalmente quais params são esperados em cada uma das rotas
Criar um arquivo navigation.d.ts
Reescrever a tipagem do módulo no arquivo
export declare global {
namespace ReactNavigation {
interface RootParamList {
myLists: undefined;
newList: undefined;
list: {
listName: string;
};
}
}
}Em alguns momentos precisamos trocar informações entre páginas da nossa aplicação, para isso, podemos utilizar a lib react-navigation e seus hooks
useNavigation()import { useNavigation } from '@react-navigation/native';const navigation = useNavigation();navigatenavigation.navigate('route', { state });É importante utilizar o hook useRoute, que também vem do react-navigation. Neste é possível desestruturar os parâmetros de dentro de route.params o ideial também é tipar QUAIS são estes params com o alias "as" + tipagem
const route = useRoute();
const { param } = route.params as RouteParams;useFocusEffect para foco na páginauseFocusEffect é bastante similar ao useEffect, entretanto ele é ativado sempre que a página recebe foco, ou seja: Além do 1o carregamento ele é invocado caso ocorra uma navegação para a página
Importação:
import { useFocusEffect } from '@react-navigation/native';Uso:
useFocusEffect(
useCallback(() => {
fetchLists();
}, [])
);
useCallBacké usado juntamente para não disparar renderizações desnecessárias, o que pode ajudar na Performance da aplicação.
Similar ao localStorage do Browser. Pode ajudar a resolver problemas de prop-drilling uma vez que, centraliza informações em um lugar.
Instalação:
npx expo instal @react-native-async-storage/async-storage
A dinâmica de lidar com AsyncStorage, similar ao localStorage, porém Assíncrono é diferente e há um padrão que pode ser utilizado.
Criação de uma pasta somente para isso storage
Criação de um arquivo storageConfig.ts para definir keys do storage
Com isso, garantimos uma melhor manutenção nas keys dos elementos salvos no AsyncStorage
const LIST_COLLECTION = '@shop-list:lists';
const ITEM_COLLECTION = '@shop-list:items';
export { LIST_COLLECTION, ITEM_COLLECTION };Exemplo: player
Arquivos:
|-list |listCreate.ts |listDelete.ts |listGetAll.ts |listGetSingle.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import { LIST_COLLECTION } from '../storageConfig';
import { ShoppingList } from '../../screens/Lists';
import { listsGetAll } from './listGetAll';
import { AppError } from '../../utils/AppError';
export async function listCreate(newList: ShoppingList) {
try {
const storedLists = await listsGetAll();
const listAlreadyExists = storedLists
.map((item: ShoppingList) => item.title)
.includes(newList.title);
if (listAlreadyExists) {
throw new AppError('Já existe uma lista com este nome.');
}
const newStorage = JSON.stringify([...storedLists, newList]);
await AsyncStorage.setItem(LIST_COLLECTION, newStorage);
console.log(storedLists);
} catch (error) {
throw error;
}
}useRef() para lidar com elementosPodemos utilizar o hook useRef() para acessar a referência de um elemento, e assim, lidar com focus(), blur(), dentre outros.
Exemplo: Ao submeter um formulário podemos usar blur() no elemento de input, afinal, o usuário já digitou o que era necessário, assim o efeito de desfoque pode encerrar o teclado aberto e remover o foco para o input.
//Criação da referência de vinculação
const newListNameInputRef = useRef<TextInput>(null);
function handleSubmit() {
///...
//Desfoca o elemento
newListNameInputRef.current?.blur();
}
//IMPORTANTE adicionar o `ref` ao elemento, caso seja um componente é necessário enviar via prop.
return (
<View>
<Input
inputRef={newListNameInputRef}
onChangeText={setNewListName}
value={newListName}
placeholder="Nome da sua lista"
autoCorrect={false}
onSubmitEditing={handleAddList}
returnKeyType="done"
/>
</View>
);Para que podemos distinguir um erro genérico/desconhecido provido por um throw e um erro reconhecido pela nossa aplicação podemos criar uma classe com um atributo message e instanciar esta classe, assim podemos recolher a instaceof <classe> dentro do bloco catch.
Exemplo:
export class AppError {
message: string;
constructor(message: string) {
this.message = message;
}
}throw new AppError('Estas lista já esta adicionada!'); catch (error) {
if (error instanceof AppError) {
Alert.alert('Nova lista', error.message);
} else {
Alert.alert('Nova lista', 'Não foi possível adicionar');
console.error(error);
}
}Por: Rafael Ramos ?