Utilizando react-native-calendars na prática
Tags: reactnative, uidesign, javascript
Published: 2024-06-03 21:05:43

React Native Calendars é um componente de calendário para iOS e Android. Ele oferece uma variedade de funcionalidades e é completamente escrito em JavaScript, o que significa que não há necessidade de código nativo.

Neste tutorial, vamos recriar no React Native uma tela que encontrei no Dribbble. Este design combina estilo e funcionalidade, e você pode conferir o design original [aqui](https://dribbble.com/shots/17884784-Doctor-Appointment-Medical-Mobile-App-UI). Vamos explorar algumas características do React Native Calendars e ver como podemos usá-lo para criar uma interface de usuário atraente e eficiente.

---

#### Pré-requisitos

Para me acompanhar nesse projeto não é necessário muito, basta ter o **NodeJS** instalado.


#### Iniciando o projeto

Nesse projeto vou estar utilizando o [Expo](https://expo.dev/), caso você não conheça, segundo a própria documentação do React Native, ele é um framework de React Native que oferece ferramentas de desenvolvimento que facilitam a criação de aplicativos, como uma biblioteca padrão de módulos nativos e muito mais.

Vamos começar criando um projeto React Native utilizando o seguinte comando no terminal:

`$ npx create-expo-app --template blank`

Caso você receba esse prompt perguntando o nome do seu app, preferencialmente não utilize nenhum acento ou caractere especial.

`? What is your app named? calendars-example`

Após ter instalado todas as dependências necessárias, vamos instalar a biblioteca **react-native-calendars**:

Primeiro, através do terminal, navegue até a pasta onde está seu projeto, e depois digite o comando abaixo:

`$ npm install --save react-native-calendars`

Com isso já podemos recriar o [design do dribble](https://dribbble.com/shots/17884784-Doctor-Appointment-Medical-Mobile-App-UI). Para estilizar vou estar utilizando o próprio **[StyleSheet](https://reactnative.dev/docs/stylesheet)** do React Native, mas você pode utilizar o que estiver mais acostumado, seja **styled-components** ou **native-wind**.

Abra o projeto no seu editor de código favorito, eu vou estar utilizando o [VSCode](https://code.visualstudio.com/). Vou começar removendo todo o código existente no arquivo **App.js** e adicionando apenas um titulo para nosso calendário:

![tela do vscode com código react-native](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1worxsw8cdz3ct9e6mp9.png)

Agora vamos rodar o projeto e ver como está ficando, para rodar basta digitar no terminal o comando abaixo (não esquece q tem que estar na pasta do projeto 😁):

`$ npx expo start`

Você vai se deparar com um QRCode que irá aparecer no seu terminal, basta escanear ele com o seu celular, mas antes, para rodar o aplicativo você vai precisar instalar o app [Expo Go](https://expo.dev/go) no seu celular, que é um aplicativo do Expo para rodar seus projetos criados com o Framework.

Após ter escaneado, e o app ter rodado, você deve estar vendo uma tela parecida com essa abaixo:

![celular iphone mostrando um app no expo go](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/md7trotfgp2babvtplxs.png)

Vamos continuar, agora criando a parte mais legal que é o calendário em si:

- Primeiro, vamos adicionar o componente de **Calendário** da biblioteca, e fazer pequenas estilizações

```javascript
import { StyleSheet, Text, SafeAreaView, View } from 'react-native';
import { Calendar, CalendarUtils } from 'react-native-calendars';

const INITIAL_DATE = CalendarUtils.getCalendarDateString(new Date());

export default function App() {
  return (
    <>
      // ✂️ Código comentado apenas para melhor visualização
      <SafeAreaView style={styles.container}>
        // ✂️ Código comentado apenas para melhor visualização
        <View style={styles.wrapper}>
          <Calendar
            current={INITIAL_DATE}
            style={styles.calendar}
          />
        </View>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  // ✂️ Código comentado apenas para melhor visualização
  wrapper: {
    paddingHorizontal: 24
  },
  calendar: {
    borderRadius: 24,
    overflow: 'hidden',
  }
});
```

Caso você esteja se perguntando o que essa linha faz:

```javascript
const INITIAL_DATE = CalendarUtils.getCalendarDateString(new Date());
```

Basicamente eu estou utilizando uma função auxiliar da própria biblioteca que transforma uma data no formato 'YYYY-MM-DD', que é o formato que o componente espera.

-  Agora vamos terminar de estilizar nosso Calendário, mudando as cores default do tema e adicionando arrows customizadas para ficar mais parecido com o design:

```javascript
import { StyleSheet, Text, SafeAreaView, View } from 'react-native';
import { Calendar, CalendarUtils } from 'react-native-calendars';
import { Feather } from '@expo/vector-icons';

const INITIAL_DATE = CalendarUtils.getCalendarDateString(new Date());

export default function App() {
  return (
    <>
      // ✂️ Código comentado apenas para melhor visualização
      <SafeAreaView style={styles.container}>
        // ✂️ Código comentado apenas para melhor visualização
        <View style={styles.wrapper}>
          <Calendar
            current={INITIAL_DATE}
            style={styles.calendar}
            theme={{
              todayTextColor: '#6567ef',
              textMonthFontWeight: 'bold',
              textDayHeaderFontWeight: 'bold',
              textMonthFontSize: 18,
              textDayFontWeight: 'bold',
              textDayFontSize: 14,
              selectedDayBackgroundColor: '#6567ef',
              selectedDayTextColor: '#ffffff',
            }}
            renderArrow={direction => {
              return (
                <View style={styles.calendarButton}>
                  <Feather name={`chevron-${direction}`} size={24} color="#6567ef" />
                </View>
              )
            }}
          />
        </View>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  // ✂️ Código comentado apenas para melhor visualização
  wrapper: {
    paddingHorizontal: 24
  },
  calendar: {
    borderRadius: 24,
    overflow: 'hidden',
  }
  calendarButton: {
    backgroundColor: '#e9e5fe',
    padding: 8,
    borderRadius: 100,
  }
});
```

- Para finalizar nosso calendário, vamos adicionar um estado para salvar a data selecionada e mostrar ela no componente:

```javascript
import { useState } from 'react';
// ✂️ Código comentado apenas para melhor visualização

export default function App() {
  const [selected, setSelected] = useState(INITIAL_DATE);

  function onDayPress(day) {
    setSelected(day.dateString);
  }

  const marked = {
    [selected]: {
      selected: true,
    }
  };

  return (
    <>
      // ✂️ Código comentado apenas para melhor visualização
      <SafeAreaView style={styles.container}>
        // ✂️ Código comentado apenas para melhor visualização
        <View style={styles.wrapper}>
          <Calendar
            // ✂️ Código comentado apenas para melhor visualização
            onDayPress={onDayPress}
            markedDates={marked}
          />
        </View>
      </SafeAreaView>
    </>
  );
}

// ✂️ Código comentado apenas para melhor visualização
```

Pronto! seu calendário deve estar parecido com esse:

![celular iphone mostrando um app no expo go com um calendário](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbhvadgvlnsif0w715n7.png)

Agora vamos colocar a parte de "Available Time" e um botão para confirmar o agendamento, o código completo dessa UI você pode ver abaixo:

```javascript
import { useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, SafeAreaView, View, Pressable, ScrollView } from 'react-native';
import { Calendar, CalendarUtils } from 'react-native-calendars';
import { Feather } from '@expo/vector-icons';

const INITIAL_DATE = CalendarUtils.getCalendarDateString(new Date());

const AVAILABLE_TIMES = [
  { id: 1, hour: '07:00 PM', status: 'available' },
  { id: 2, hour: '07:15 PM', status: 'unavailable' },
  { id: 3, hour: '07:30 PM', status: 'available' },
  { id: 4, hour: '08:30 PM', status: 'unavailable' },
  { id: 5, hour: '08:45 PM', status: 'available' },
  { id: 6, hour: '09:00 PM', status: 'available' },
]

export default function App() {
  const [selected, setSelected] = useState(INITIAL_DATE);
  const [timeSelected, setTimeSelected] = useState(null);

  function onDayPress(day) {
    setSelected(day.dateString);
  }

  const marked = {
    [selected]: {
      selected: true,
    }
  };

  return (
    <>
      <StatusBar style="auto" />

      <SafeAreaView style={styles.container}>
        <ScrollView>
          <Text style={styles.title}>Appointment Booking</Text>
      
          <View style={styles.wrapper}>
            <Calendar
              theme={{
                todayTextColor: '#6567ef',
                textMonthFontWeight: 'bold',
                textDayHeaderFontWeight: 'bold',
                textMonthFontSize: 18,
                textDayFontWeight: 'bold',
                textDayFontSize: 14,
                selectedDayBackgroundColor: '#6567ef',
                selectedDayTextColor: '#ffffff',
              }}
              enableSwipeMonths
              current={INITIAL_DATE}
              minDate={INITIAL_DATE}
              style={styles.calendar}
              onDayPress={onDayPress}
              markedDates={marked}
              renderArrow={direction => {
                return (
                  <View style={styles.calendarButton}>
                    <Feather name={`chevron-${direction}`} size={24} color="#6567ef" />
                  </View>
                )
              }}
            />

            <Text style={styles.sectionTitle}>Available Time</Text>
            
            <View style={styles.availableTimeList}>
              {AVAILABLE_TIMES.map(({ id, hour, status }) => {
                const isTimeEnabled = status === 'available'
                const isTimeSelected = timeSelected === hour

                const defaultBg = isTimeEnabled ? '#e9e5fe' : '#ebe9f6'
                const defaultColor = isTimeEnabled ? '#222222' : '#aeaeb0'

                return (
                  <Pressable
                    key={id}
                    style={[
                      styles.availableTimeButton,
                      { backgroundColor: isTimeSelected ? '#6567ef' : defaultBg }
                    ]}
                    onPress={() => setTimeSelected(hour)}
                    disabled={!isTimeEnabled}
                  >
                    <Text
                      style={[
                        styles.availableTimeButtonText,
                        { color: isTimeSelected ? '#ffffff' : defaultColor }
                      ]}>
                      {hour}
                    </Text>
                  </Pressable>
                )
              })}
            </View>

            <Pressable style={styles.ConfirmScheduleButton}>
              <Text style={styles.ConfirmScheduleButtonText}>Confirm Schedule</Text>
            </Pressable>
          </View>
        </ScrollView>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f6fa',
  },
  title: {
    marginHorizontal: 'auto',
    fontWeight: 'bold',
    marginVertical: 24,
    fontSize: 18,
  },
  wrapper: {
    paddingHorizontal: 24
  },
  calendar: {
    borderRadius: 24,
    overflow: 'hidden',
  },
  calendarButton: {
    backgroundColor: '#e9e5fe',
    padding: 8,
    borderRadius: 100,
  },
  sectionTitle: {
    fontWeight: 'bold',
    marginVertical: 24,
    fontSize: 16,
  },
  availableTimeList: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
    marginBottom: 64,
  },
  availableTimeButton: {
    width: '30%',
    marginBottom: 6,
    paddingVertical: 16,
    borderRadius: 999,
    alignSelf: 'flex-start',
    alignItems: 'center'
  },
  availableTimeButtonText: {
    fontWeight: 'bold',
    fontSize: 12
  },
  ConfirmScheduleButton: {
    backgroundColor: '#6567ef',
    padding: 22,
    borderRadius: 999,
    alignItems: 'center'
  },
  ConfirmScheduleButtonText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#ffffff'
  }
});
```

Sua tela final deve estar parecida com essa abaixo:

![celular iphone mostrando um app no expo go com um calendário](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vcutme5k2zzxzr8oj60n.png)

Espero que tenha gostado, você pode [acessar aqui](https://github.com/MarcosWillianR/calendars-example) o repositório com o código final caso esteja tendo dificuldades.

Sinta-se à vontade para sugerir uma biblioteca especifica para mim construir algo utilizando o Dribble. Obrigado!