<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Grossi's Blog]]></title><description><![CDATA[Grossi's Blog]]></description><link>https://grossis.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 06 Nov 2026 03:02:22 GMT</lastBuildDate><atom:link href="https://grossis.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Conhecendo o widget DraggableScrollableSheet do Flutter]]></title><description><![CDATA[Me foi passada uma tarefa no trabalho para desenvolver um componente no Flutter que é bastante utilizado em outros apps também, mas tinha um pequeno…]]></description><link>https://grossis.hashnode.dev/conhecendo-o-widget-draggablescrollablesheet-do-flutter</link><guid isPermaLink="true">https://grossis.hashnode.dev/conhecendo-o-widget-draggablescrollablesheet-do-flutter</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Flutter Widgets]]></category><dc:creator><![CDATA[Caio Grossi]]></dc:creator><pubDate>Mon, 17 Oct 2022 23:56:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/_RgxKuLEmsc/upload/v1666050511337/IWjyC9dve6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-motivacao">Motivação</h1>
<p>Me foi passada uma tarefa no trabalho para desenvolver um componente no Flutter que é bastante utilizado em outros apps também, mas tinha um pequeno problema: eu não fazia nem ideia de qual era o nome desse componente e de como eu iria implementar isso no app. Levei pouco tempo para descobrir o nome dele (DraggableScrollableSheet) e já comecei a implementar no app e cuidar das particularidades que eu precisava, que vou contar nesse post.</p>
<h1 id="heading-entendendo-o-componente-draggablescrollablesheet-e-definindo-as-particularidades-que-eu-precisava">Entendendo o componente DraggableScrollableSheet e definindo as particularidades que eu precisava</h1>
<p>Vocês com certeza já devem ter utilizado o Google Maps, correto? Então, esse é um dos apps mais famosos que contam com um componente nesse estilo aqui: </p>
<p><img src="https://raw.githubusercontent.com/NocheVolta/FramerBottomSheet/master/bottomSheet.gif" alt="gif" /></p>
<p>Sim, esse não é o google maps, mas peguei o primeiro exemplo que achei aqui para ilustrar mais ou menos o que eu precisava. Então, vamos a lista de requisitos:</p>
<ul>
<li>Um componente que ficasse "grudado" na parte de baixo da tela e <em>NUNCA</em> ficasse completamente escondido;</li>
<li>Um componente que se expandisse em direção ao topo da tela quando eu clicasse em um botão ou arrastasse ele em direção ao topo;</li>
<li>Um componente que o conteúdo dele fosse "scrolllavel" para visualização das informações;</li>
</ul>
<p>Com esses requisitos, iriamos tender para duas soluções dentro do flutter: <a target="_blank" href="https://api.flutter.dev/flutter/material/showModalBottomSheet.html">BottomSheet</a> ou <a target="_blank" href="https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html">DraggableScrollableSheet</a>. Acabei ficando com o DraggableScrollableSheet pelo simples motivo de que com ele é possível não deixa-lo completamente escondido, diferente do outro.</p>
<p>Com o componente escolhido, vamos para a implementação!</p>
<h1 id="heading-implementando-o-draggablescrollablesheet">Implementando o DraggableScrollableSheet</h1>
<h2 id="heading-necessidade-do-widget-stack">Necessidade do widget Stack</h2>
<p>Se você for na <a target="_blank" href="https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html">documentação oficial</a> desse widget, vai achar esse código:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomePage({<span class="hljs-built_in">super</span>.key});

  @override
  Wid<span class="hljs-keyword">get</span> <span class="hljs-title">build</span>(<span class="hljs-params">BuildContext context</span>) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'DraggableScrollableSheet'</span>),
      ),
      <span class="hljs-attr">body</span>: SizedBox.expand(
        child: DraggableScrollableSheet(
          builder: (BuildContext context, ScrollController scrollController) {
            <span class="hljs-keyword">return</span> Container(
              color: Colors.blue[<span class="hljs-number">100</span>],
              <span class="hljs-attr">child</span>: ListView.builder(
                controller: scrollController,
                <span class="hljs-attr">itemCount</span>: <span class="hljs-number">25</span>,
                <span class="hljs-attr">itemBuilder</span>: (BuildContext context, int index) {
                  <span class="hljs-keyword">return</span> ListTile(title: Text(<span class="hljs-string">'Item $index'</span>));
                },
              ),
            );
          },
        ),
      ),
    );
  }
}
</code></pre><p>que mostra o básico, mas não mostra como podemos utilizar ele com outros conteúdos na tela. Para isso, vamos precisar da ajuda do widget <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Stack-class.html">Stack</a>.Não vou entrar em muitos detalhes aqui, mas o Stack basicamente permite que seus filhos se sobreponham um sobre o outro (tipo o <strong>position: absolute</strong> do css). E o componente DraggableScrollableSheet é inteligente o suficiente para saber que quando o pai dele for um Stack, ele deve ficar colado na base da tela. Com isso, nosso código atualizado ficaria assim:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomePage({<span class="hljs-built_in">super</span>.key});

  @override
  Wid<span class="hljs-keyword">get</span> <span class="hljs-title">build</span>(<span class="hljs-params">BuildContext context</span>) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'DraggableScrollableSheet'</span>),
      ),
      <span class="hljs-attr">body</span>: Stack( <span class="hljs-comment">// &lt;-- COLOCANDO STACK COMO PAI</span>
        children: [
          Text(<span class="hljs-string">"Filho de exemplo"</span>), <span class="hljs-comment">// &lt;-- EXEMPLO DE CONTEÚDO </span>
          DraggableScrollableSheet(
            builder: (BuildContext context, ScrollController scrollController) {
              <span class="hljs-keyword">return</span> Container(
                color: Colors.blue[<span class="hljs-number">100</span>],
                <span class="hljs-attr">child</span>: ListView.builder(
                  controller: scrollController,
                  <span class="hljs-attr">itemCount</span>: <span class="hljs-number">25</span>,
                  <span class="hljs-attr">itemBuilder</span>: (BuildContext context, int index) {
                    <span class="hljs-keyword">return</span> ListTile(title: Text(<span class="hljs-string">'Item $index'</span>));
                  },
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
</code></pre><p>com isso, podemos colocar mais elementos na nossa tela e prosseguir com o desenvolvimento. No momento, está assim:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665356231872/WYJUiQhNC.gif" alt="flutter2.gif" /></p>
<h2 id="heading-construindo-o-header-do-draggablescrollablesheet">Construindo o header do DraggableScrollableSheet</h2>
<p>Bom, agora que já temos algo funcional que cumpre os nosso requisitos básicos de ficar preso na parte de baixo da tela e conseguir se expandir em direção ao topo, vamos ao header do nosso componente. O header é uma parte bem simples, vai conter basicamente um resumo do que o usuário está vendo na tela e um botão para ele clicar e poder expandir completamente ou voltar ao seu estado inicial (aparecendo só uma parte). Sabendo disso, o nosso código com o header ficará algo assim:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:math'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomePage({<span class="hljs-built_in">super</span>.key});

  @override
  Wid<span class="hljs-keyword">get</span> <span class="hljs-title">build</span>(<span class="hljs-params">BuildContext context</span>) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'DraggableScrollableSheet'</span>),
      ),
      <span class="hljs-attr">body</span>: Stack(
        children: [
          Text(<span class="hljs-string">"Filho de exemplo"</span>),
          DraggableScrollableSheet(
            initialChildSize: <span class="hljs-number">0.12</span>, <span class="hljs-comment">// &lt;-- NOVAS PROPRIEDADES</span>
            <span class="hljs-attr">minChildSize</span>: <span class="hljs-number">0.12</span>,
            <span class="hljs-attr">maxChildSize</span>: <span class="hljs-number">0.8</span>,
            <span class="hljs-attr">builder</span>: (BuildContext context, ScrollController scrollController) {
              <span class="hljs-keyword">return</span> Container(
                color: Colors.blue,
                <span class="hljs-attr">child</span>: SingleChildScrollView( <span class="hljs-comment">// &lt;-- MUDANÇA DO WIDGET DE SCROLL</span>
                  controller: scrollController,
                  <span class="hljs-attr">child</span>: Column(
                    children: [
                      Container(
                        padding:
                            <span class="hljs-keyword">const</span> EdgeInsets.only(right: <span class="hljs-number">18</span>, <span class="hljs-attr">left</span>: <span class="hljs-number">18</span>, <span class="hljs-attr">top</span>: <span class="hljs-number">12</span>),
                        <span class="hljs-attr">child</span>: Row( <span class="hljs-comment">// &lt;-- ROW QUE SERÁ O HEADER</span>
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          <span class="hljs-attr">children</span>: [
                            Column( <span class="hljs-comment">// &lt;-- COLUNA DE RESUMO</span>
                              crossAxisAlignment: CrossAxisAlignment.start,
                              <span class="hljs-attr">children</span>: <span class="hljs-keyword">const</span> [
                                Text(
                                  <span class="hljs-string">"Resumo de algo aqui"</span>,
                                  <span class="hljs-attr">style</span>: TextStyle(color: Colors.white),
                                ),
                                Text(
                                  <span class="hljs-string">"Descrição do resumo aq"</span>,
                                  <span class="hljs-attr">style</span>: TextStyle(
                                    color: Colors.white,
                                    <span class="hljs-attr">fontWeight</span>: FontWeight.w700,
                                  ),
                                )
                              ],
                            ),
                            OutlinedButton( <span class="hljs-comment">// &lt;-- BOTÃO PARA EXPANDIR E ENCOLHER</span>
                              onPressed: () {},
                              <span class="hljs-attr">style</span>: OutlinedButton.styleFrom(
                                side: <span class="hljs-keyword">const</span> BorderSide(
                                    width: <span class="hljs-number">1.0</span>, <span class="hljs-attr">color</span>: Colors.white),
                                <span class="hljs-attr">shape</span>: <span class="hljs-keyword">const</span> StadiumBorder(),
                              ),
                              <span class="hljs-attr">child</span>: Row(
                                children: [
                                  <span class="hljs-keyword">const</span> Text(
                                    <span class="hljs-string">"Ver mais"</span>,
                                    <span class="hljs-attr">style</span>: TextStyle(color: Colors.white),
                                  ),
                                  <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">5</span>),
                                  Transform.rotate(
                                    angle: <span class="hljs-number">-90</span> * pi / <span class="hljs-number">180</span>,
                                    <span class="hljs-attr">child</span>: <span class="hljs-keyword">const</span> Icon(
                                      Icons.arrow_forward_ios_rounded,
                                      <span class="hljs-attr">size</span>: <span class="hljs-number">18.0</span>,
                                      <span class="hljs-attr">color</span>: Colors.white,
                                    ),
                                  ),
                                ],
                              ),
                            )
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
</code></pre><p>Tivemos uma certa quantidade de mudanças, sendo:</p>
<ul>
<li><p>Propriedades de <strong>initialChildSize</strong>, <strong>minChildSize</strong> e <strong>maxChildSize</strong>. O nome já é bem descritivo, sendo a altura inicial do widget, mínima e máxima respectivamente. Esse valor pode variar de 0 até 1, correspondendo a % da altura do componente pai.</p>
</li>
<li><p>Mudança para de <strong>ListView</strong> para <strong>SingleChildScrollView</strong>. Isso aqui não afeta tanto o nosso desenvolvimento, eu resolvi mudar pois fazia mais sentido para o que eu estava desenvolvendo.</p>
</li>
<li><p>Componente <strong>Row</strong> que será o nosso header com o resumo do que está sendo apresentado na tela e com o botão para expandir e encolher o nosso widget (cuidaremos do comportamento desse botão mais tarde).</p>
</li>
</ul>
<p>Com isso, teremos o seguinte resultado:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665357529269/j_BxhOaKm.gif" alt="flutter3.gif" /></p>
<p>Com isso feito, vamos ao funcionamento do botão.</p>
<h2 id="heading-desenvolvendo-o-funcionamento-do-botao-de-expandir-e-encolher">Desenvolvendo o funcionamento do botão de expandir e encolher</h2>
<p>Já que esse botão é de expandir e encolher, vamos precisar de 2 coisas para termos um funcionamento completo:</p>
<ul>
<li>Guardar se o nosso widget (DraggableScrollableSheet) está aberto ou fechado;</li>
<li>Quando clicarmos no botão, ele expandir ou encolher o nosso widget;</li>
</ul>
<p>Cuidaremos dos dois em seguida.</p>
<p>Se você conhece o básico de flutter, já sabe que a primeira coisa que vamos ter que fazer é transformar o nosso componente de Stateless para Stateful (o que nos permite guardar estado e ter acesso aos <em>life cyclie methods</em> de um widget, vamos precisar de ambos). Se você está no VSCode e clicar com <strong>CTRl + .</strong> em <strong>StatelessWidget</strong>, verá que aparece uma opção de você transformar em <strong>Stateful</strong>, assim:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665358177243/oLORaYkFf.png" alt="image.png" /></p>
<p>Após a conversão, ficará assim:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:math'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomePage({<span class="hljs-built_in">super</span>.key});

  @override
  State&lt;HomePage&gt; createState() =&gt; _HomePageState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomePage</span>&gt; </span>{
  @override
  Wid<span class="hljs-keyword">get</span> <span class="hljs-title">build</span>(<span class="hljs-params">BuildContext context</span>) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'DraggableScrollableSheet'</span>),
      ),
      <span class="hljs-attr">body</span>: Stack(
        children: [
          Text(<span class="hljs-string">"Filho de exemplo"</span>),
          DraggableScrollableSheet(
            initialChildSize: <span class="hljs-number">0.12</span>,
            <span class="hljs-attr">minChildSize</span>: <span class="hljs-number">0.12</span>,
            <span class="hljs-attr">maxChildSize</span>: <span class="hljs-number">0.8</span>,
            <span class="hljs-attr">builder</span>: (BuildContext context, ScrollController scrollController) {
              <span class="hljs-keyword">return</span> Container(
                color: Colors.blue,
                <span class="hljs-attr">child</span>: SingleChildScrollView(
                  controller: scrollController,
                  <span class="hljs-attr">child</span>: Column(
                    children: [
                      Container(
                        padding:
                            <span class="hljs-keyword">const</span> EdgeInsets.only(right: <span class="hljs-number">18</span>, <span class="hljs-attr">left</span>: <span class="hljs-number">18</span>, <span class="hljs-attr">top</span>: <span class="hljs-number">12</span>),
                        <span class="hljs-attr">child</span>: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          <span class="hljs-attr">children</span>: [
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              <span class="hljs-attr">children</span>: <span class="hljs-keyword">const</span> [
                                Text(
                                  <span class="hljs-string">"Resumo de algo aqui"</span>,
                                  <span class="hljs-attr">style</span>: TextStyle(color: Colors.white),
                                ),
                                Text(
                                  <span class="hljs-string">"Descrição do resumo aq"</span>,
                                  <span class="hljs-attr">style</span>: TextStyle(
                                    color: Colors.white,
                                    <span class="hljs-attr">fontWeight</span>: FontWeight.w700,
                                  ),
                                )
                              ],
                            ),
                            OutlinedButton(
                              onPressed: () {},
                              <span class="hljs-attr">style</span>: OutlinedButton.styleFrom(
                                side: <span class="hljs-keyword">const</span> BorderSide(
                                    width: <span class="hljs-number">1.0</span>, <span class="hljs-attr">color</span>: Colors.white),
                                <span class="hljs-attr">shape</span>: <span class="hljs-keyword">const</span> StadiumBorder(),
                              ),
                              <span class="hljs-attr">child</span>: Row(
                                children: [
                                  <span class="hljs-keyword">const</span> Text(
                                    <span class="hljs-string">"Ver mais"</span>,
                                    <span class="hljs-attr">style</span>: TextStyle(color: Colors.white),
                                  ),
                                  <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">5</span>),
                                  Transform.rotate(
                                    angle: <span class="hljs-number">-90</span> * pi / <span class="hljs-number">180</span>,
                                    <span class="hljs-attr">child</span>: <span class="hljs-keyword">const</span> Icon(
                                      Icons.arrow_forward_ios_rounded,
                                      <span class="hljs-attr">size</span>: <span class="hljs-number">18.0</span>,
                                      <span class="hljs-attr">color</span>: Colors.white,
                                    ),
                                  ),
                                ],
                              ),
                            )
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
</code></pre><p>Com isso, podemos colocar nosso estado bem no começo da classe <strong>_HomePageState</strong>, bem assim:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomePage</span>&gt; </span>{
  bool isBottomSheetOpen = <span class="hljs-literal">false</span>;

<span class="hljs-comment">// Resto do código que já conhecemos aqui ...</span>
</code></pre><p>Perfeito, agora temos um meio de guardar se ele ta aberto, só colocar uma função de onPressed no botão para abrir ou fechar o widget e mudar esse estado, correto? </p>
<p><strong>ERRADO !</strong></p>
<p>Atualmente já temos um modo de abrir o nosso widget: arrastando ele pra cima! Se mudarmos esse estado somente quando um botão for clicado, se abrirmos ele arrastando, o nosso estado não vai saber que ele está aberto e continuará mostrando o texto do botão como se ele estivesse fechado. Para resolver isso vamos precisar do <a target="_blank" href="https://api.flutter.dev/flutter/widgets/DraggableScrollableController-class.html">DraggableScrollableController</a>. Esse controller vai nos permitir fazer duas coisas: abrir o widget quando o botão for clicado e saber em que altura o nosso widget está enquanto ele está sendo arrastando (permitindo a gente saber se o widget está encolhido ou totalmente aberto). A inicialização dele é bem simples, vamos colocar logo abaixo do nosso estado, assim:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomePage</span>&gt; </span>{
  bool isBottomSheetOpen = <span class="hljs-literal">false</span>;
DraggableScrollableController dragController =
      DraggableScrollableController();

<span class="hljs-comment">// Resto do código que já conhecemos aqui ...</span>
</code></pre><p>e claro, vamos atrelar ele ao nosso widget:</p>
<pre><code><span class="hljs-comment">// Resto do código que já conhecemos aqui ...</span>
DraggableScrollableSheet(
    initialChildSize: <span class="hljs-number">0.12</span>,
    <span class="hljs-attr">minChildSize</span>: <span class="hljs-number">0.12</span>,
    <span class="hljs-attr">maxChildSize</span>: <span class="hljs-number">0.8</span>,
    <span class="hljs-attr">controller</span>: dragController,
    <span class="hljs-attr">builder</span>: (BuildContext context, ScrollController scrollController) {
<span class="hljs-comment">// Resto do código que já conhecemos aqui ...</span>
</code></pre><p>Agora que já temos o controller inicializado e atrelado ao nosso widget, precisamos criar uma função para abrir e fechar, atrelar ao nosso botão e mudar o texto e ícone dependendo do estado atual, ficando assim:</p>
<ul>
<li>A função:<pre><code>onToggleBottomSheet() {
 <span class="hljs-keyword">if</span> (isBottomSheetOpen) {
   dragController.animateTo(<span class="hljs-number">0.12</span>,
       <span class="hljs-attr">duration</span>: <span class="hljs-keyword">const</span> Duration(milliseconds: <span class="hljs-number">200</span>), <span class="hljs-attr">curve</span>: Curves.ease);
   isBottomSheetOpen = <span class="hljs-literal">false</span>;
 } <span class="hljs-keyword">else</span> {
   dragController.animateTo(<span class="hljs-number">0.8</span>,
       <span class="hljs-attr">duration</span>: <span class="hljs-keyword">const</span> Duration(milliseconds: <span class="hljs-number">200</span>), <span class="hljs-attr">curve</span>: Curves.ease);
   isBottomSheetOpen = <span class="hljs-literal">true</span>;
 }
}
</code></pre></li>
<li>A atribuição ao botão:</li>
</ul>
<pre><code>OutlinedButton(
  onPressed: onToggleBottomSheet,
</code></pre><ul>
<li>A mudança do texto e ícone refletindo o estado:</li>
</ul>
<pre><code>OutlinedButton(
      onPressed: onToggleBottomSheet,
      <span class="hljs-attr">style</span>: OutlinedButton.styleFrom(
      side: <span class="hljs-keyword">const</span> BorderSide(
          width: <span class="hljs-number">1.0</span>, <span class="hljs-attr">color</span>: Colors.white),
      <span class="hljs-attr">shape</span>: <span class="hljs-keyword">const</span> StadiumBorder(),
      ),
      <span class="hljs-attr">child</span>: Row(
        children: [
          Text(
            isBottomSheetOpen
                ? <span class="hljs-string">"Ver menos"</span>
                : <span class="hljs-string">"Ver mais"</span>,
            <span class="hljs-attr">style</span>: <span class="hljs-keyword">const</span> TextStyle(
                color: Colors.white),
          ),
          <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">5</span>),
          Transform.rotate(
            angle: isBottomSheetOpen
                ? <span class="hljs-number">90</span> * pi / <span class="hljs-number">180</span>
                : <span class="hljs-number">-90</span> * pi / <span class="hljs-number">180</span>,
            <span class="hljs-attr">child</span>: <span class="hljs-keyword">const</span> Icon(
              Icons.arrow_forward_ios_rounded,
              <span class="hljs-attr">size</span>: <span class="hljs-number">18.0</span>,
              <span class="hljs-attr">color</span>: Colors.white,
            ),
          ),
        ],
      ),
)
</code></pre><p>Com isso feito, teremos o seguinte resultado em tela:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665359437986/cBner2Y08.gif" alt="flutter4.gif" /></p>
<p>Perfeito! Nosso botão está mudando o texto de acordo com o nosso estado e o nosso widget está abrindo ou fechando ao clicar no botão, mas ainda temos um problema...
Se eu abrir o widget arrastando ele para cima, o nosso estado não fica sabendo disso e causando isso aqui:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665359639516/X2I4K7pn0.gif" alt="flutter5.gif" /></p>
<p>Por sorte isso é fácil de resolver com o nosso controller, vamos precisar colocar um listener nele para toda hora que a altura do nosso widget mudar na tela ele nos avisar e podermos saber se ele está fechado ou aberto enquanto o nosso usuário arrasta. Vamos precisar adicionar esse listener quando o widget é montado na tela, ou seja, no método <strong>initState</strong> do nosso widget. Esse é o resultado:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomePage</span>&gt; </span>{
  bool isBottomSheetOpen = <span class="hljs-literal">false</span>;
  DraggableScrollableController dragController =
      DraggableScrollableController();

  @override
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-built_in">super</span>.initState();

    dragController.addListener(
      () {
        <span class="hljs-keyword">if</span> (dragController.size &gt; <span class="hljs-number">0.12</span> &amp;&amp; isBottomSheetOpen == <span class="hljs-literal">false</span>) {
          isBottomSheetOpen = <span class="hljs-literal">true</span>;
        }

        <span class="hljs-keyword">if</span> (dragController.size == <span class="hljs-number">0.12</span> &amp;&amp; isBottomSheetOpen == <span class="hljs-literal">true</span>) {
          isBottomSheetOpen = <span class="hljs-literal">false</span>;
        }
      },
    );
  }
</code></pre><p>toda vez que a altura do nosso widget muda, ele chama esse listener verifica se o tamanho atual é maior que o mínimo (0.12) ou igual e muda o nosso estado de acordo com isso. Com isso, temos o resultado final:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665360429808/eJwzrl_j4.gif" alt="flutter6.gif" /></p>
<h2 id="heading-deixando-o-nosso-header-preso-no-topo-do-widget">Deixando o nosso header preso no topo do widget</h2>
<p>Perfeito, fizemos tudo funcionar, mas ainda sim algo pode ser melhorado. Se adicionarmos mais conteúdo ao nosso widget, ele poderá scrollar mais ainda e teremos algo assim:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665361810246/FE7rZjON-.gif" alt="flutter7.gif" /></p>
<p>ou seja, nosso header esta subindo junto com todo conteúdo. Já que ele é um header com um botão de ação, o ideal seria ele ficar preso no topo enquanto scrollamos o resto do coteúdo, correto? </p>
<p>Para isso, vamos precisar do widget <strong>StickyHeader</strong> que adquirimos por meio do pacote <a target="_blank" href="https://pub.dev/packages/sticky_headers">sticky_headers</a>. Vamos adiciona-lo nas nossas dependências:</p>
<pre><code>dependencies:
  flutter:
    sdk: flutter
  <span class="hljs-attr">sticky_headers</span>: <span class="hljs-string">"^0.3.0"</span>
</code></pre><p>A utilização dele é bem simples, levando apenas um argumento de <strong>header</strong> que é o que vai ficar preso e um <strong>content</strong> que é todo o resto, nos levando para esse código final:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:math'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:sticky_headers/sticky_headers/widget.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomePage({<span class="hljs-built_in">super</span>.key});

  @override
  State&lt;HomePage&gt; createState() =&gt; _HomePageState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomePage</span>&gt; </span>{
  bool isBottomSheetOpen = <span class="hljs-literal">false</span>;
  DraggableScrollableController dragController =
      DraggableScrollableController();

  @override
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-built_in">super</span>.initState();

    dragController.addListener(
      () {
        <span class="hljs-keyword">if</span> (dragController.size &gt; <span class="hljs-number">0.12</span> &amp;&amp; isBottomSheetOpen == <span class="hljs-literal">false</span>) {
          isBottomSheetOpen = <span class="hljs-literal">true</span>;
        }

        <span class="hljs-keyword">if</span> (dragController.size == <span class="hljs-number">0.12</span> &amp;&amp; isBottomSheetOpen == <span class="hljs-literal">true</span>) {
          isBottomSheetOpen = <span class="hljs-literal">false</span>;
        }
      },
    );
  }

  onToggleBottomSheet() {
    <span class="hljs-keyword">if</span> (isBottomSheetOpen) {
      dragController.animateTo(<span class="hljs-number">0.12</span>,
          <span class="hljs-attr">duration</span>: <span class="hljs-keyword">const</span> Duration(milliseconds: <span class="hljs-number">200</span>), <span class="hljs-attr">curve</span>: Curves.ease);
      isBottomSheetOpen = <span class="hljs-literal">false</span>;
    } <span class="hljs-keyword">else</span> {
      dragController.animateTo(<span class="hljs-number">0.8</span>,
          <span class="hljs-attr">duration</span>: <span class="hljs-keyword">const</span> Duration(milliseconds: <span class="hljs-number">200</span>), <span class="hljs-attr">curve</span>: Curves.ease);
      isBottomSheetOpen = <span class="hljs-literal">true</span>;
    }
  }

  @override
  Wid<span class="hljs-keyword">get</span> <span class="hljs-title">build</span>(<span class="hljs-params">BuildContext context</span>) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'DraggableScrollableSheet'</span>),
      ),
      <span class="hljs-attr">body</span>: Stack(
        children: [
          Text(<span class="hljs-string">"Filho de exemplo"</span>),
          DraggableScrollableSheet(
            initialChildSize: <span class="hljs-number">0.12</span>,
            <span class="hljs-attr">minChildSize</span>: <span class="hljs-number">0.12</span>,
            <span class="hljs-attr">maxChildSize</span>: <span class="hljs-number">0.8</span>,
            <span class="hljs-attr">controller</span>: dragController,
            <span class="hljs-attr">builder</span>: (BuildContext context, ScrollController scrollController) {
              <span class="hljs-keyword">return</span> Container(
                color: Colors.blue,
                <span class="hljs-attr">child</span>: SingleChildScrollView(
                  controller: scrollController,
                  <span class="hljs-attr">child</span>: StickyHeader( <span class="hljs-comment">// &lt;-- O WIDGET</span>
                    header: Container(
                      padding:
                          <span class="hljs-keyword">const</span> EdgeInsets.only(right: <span class="hljs-number">18</span>, <span class="hljs-attr">left</span>: <span class="hljs-number">18</span>, <span class="hljs-attr">top</span>: <span class="hljs-number">12</span>),
                      <span class="hljs-attr">color</span>: Colors.blue,
                      <span class="hljs-attr">child</span>: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        <span class="hljs-attr">children</span>: [
                          Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            <span class="hljs-attr">children</span>: <span class="hljs-keyword">const</span> [
                              Text(
                                <span class="hljs-string">"Resumo de algo aqui"</span>,
                                <span class="hljs-attr">style</span>: TextStyle(color: Colors.white),
                              ),
                              Text(
                                <span class="hljs-string">"Descrição do resumo aq"</span>,
                                <span class="hljs-attr">style</span>: TextStyle(
                                  color: Colors.white,
                                  <span class="hljs-attr">fontWeight</span>: FontWeight.w700,
                                ),
                              )
                            ],
                          ),
                          OutlinedButton(
                            onPressed: onToggleBottomSheet,
                            <span class="hljs-attr">style</span>: OutlinedButton.styleFrom(
                              side: <span class="hljs-keyword">const</span> BorderSide(
                                  width: <span class="hljs-number">1.0</span>, <span class="hljs-attr">color</span>: Colors.white),
                              <span class="hljs-attr">shape</span>: <span class="hljs-keyword">const</span> StadiumBorder(),
                            ),
                            <span class="hljs-attr">child</span>: Row(
                              children: [
                                Text(
                                  isBottomSheetOpen ? <span class="hljs-string">"Ver menos"</span> : <span class="hljs-string">"Ver mais"</span>,
                                  <span class="hljs-attr">style</span>: <span class="hljs-keyword">const</span> TextStyle(color: Colors.white),
                                ),
                                <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">5</span>),
                                Transform.rotate(
                                  angle: isBottomSheetOpen
                                      ? <span class="hljs-number">90</span> * pi / <span class="hljs-number">180</span>
                                      : <span class="hljs-number">-90</span> * pi / <span class="hljs-number">180</span>,
                                  <span class="hljs-attr">child</span>: <span class="hljs-keyword">const</span> Icon(
                                    Icons.arrow_forward_ios_rounded,
                                    <span class="hljs-attr">size</span>: <span class="hljs-number">18.0</span>,
                                    <span class="hljs-attr">color</span>: Colors.white,
                                  ),
                                ),
                              ],
                            ),
                          )
                        ],
                      ),
                    ),
                    <span class="hljs-attr">content</span>: Padding(
                      padding: EdgeInsets.all(<span class="hljs-number">16</span>),
                      <span class="hljs-attr">child</span>: Column(children: [
                        Text(<span class="hljs-string">"Filho de exemplo"</span>),
                      ]),
                    ),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
</code></pre><p>e esse resultado:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665363702978/qS9b86FEy.gif" alt="flutter8.gif" /></p>
<h1 id="heading-conclusao">Conclusão</h1>
<p>Por não ter achado muita coisa igual é com os outros widgets do flutter enquanto eu estava tendo que implementar esse, achei que seria legal trazer o desenvolvimento del aqui, ainda mais com esse controle por botão e sempre assistindo mudança de estado dele. Foi algo interessante de implementar e me fez conhecer ainda mais o flutter.</p>
<p>CÓDIGO FONTE COMPLETO <a target="_blank" href="https://github.com/CaioGrossi/DraggableScrollableSheet-example">AQUI</a>.</p>
]]></content:encoded></item><item><title><![CDATA[O que é um patch e como ele pode ser uma mão na roda]]></title><description><![CDATA[Recentemente foi aprovada a utilização do CMS Strapi aqui na empresa para poder delegar coisas simples como edição de texto, cor e imagens nas páginas de…]]></description><link>https://grossis.hashnode.dev/o-que-e-um-patch-e-como-ele-pode-ser-uma-mao-na-roda</link><guid isPermaLink="true">https://grossis.hashnode.dev/o-que-e-um-patch-e-como-ele-pode-ser-uma-mao-na-roda</guid><category><![CDATA[cms]]></category><category><![CDATA[Strapi]]></category><dc:creator><![CDATA[Caio Grossi]]></dc:creator><pubDate>Tue, 26 Apr 2022 20:32:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/TWoL-QCZubY/upload/v1651004681119/dCC5P3iha.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-motivacao">Motivação</h1>
<p>Recentemente foi aprovada a utilização do CMS Strapi aqui na empresa para poder delegar coisas simples como edição de texto, cor e imagens nas páginas de produto. Basicamente vamos entregar a linda dashboard do Strapi para a galera do marketing e deixar eles mesmos mudarem texto, cor e imagens na páginas sem a necessidade de um desenvolvedor (claro que com seus devidos cuidados). Isso ainda vai ser assunto para outro post aqui.</p>
<p>Quando fui apresentar o Strapi, uma feature que por padrão ele não tem foi requisitada: versionamento de conteúdo. Felizmente, ele tem um plugin que faz exatamente isso. Ao instalar esse plugin, percebi que o próprio autor faz um pequeno "hack" para sobrescrever o comportamento padrão do Strapi, ai entra o patch.</p>
<h1 id="heading-o-que-e-um-patch">O que é um patch?</h1>
<p>Um <a target="_blank" href="https://www.oreilly.com/library/view/git-pocket-guide/9781449327507/ch11.html">patch</a> nada mais é do que uma representação das diferenças entre dois arquivos. Considerando os arquivos  <strong>A</strong> e <strong>B</strong>, um patch dos arquivos <strong>A &gt; B</strong> é uma descrição do que deve ser feito para transformar o arquivo <strong>A</strong> em <strong>B</strong>. Um exemplo de patch gerado pelo <strong>git diff</strong>:</p>
<pre><code>diff --git a/foo.c b/foo.c
index <span class="hljs-number">30</span>cfd169.<span class="hljs-number">.8</span>de130c2 <span class="hljs-number">100644</span>
--- a/foo.c
+++ b/foo.c
@@ <span class="hljs-number">-1</span>,<span class="hljs-number">5</span> +<span class="hljs-number">1</span>,<span class="hljs-number">5</span> @@
 <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;string.h&gt;</span></span>

 <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">check</span> <span class="hljs-params">(<span class="hljs-keyword">char</span> *<span class="hljs-built_in">string</span>)</span> </span>{
-    <span class="hljs-keyword">return</span> !<span class="hljs-built_in">strcmp</span>(<span class="hljs-built_in">string</span>, <span class="hljs-string">"ok"</span>);
+    <span class="hljs-keyword">return</span> (<span class="hljs-built_in">string</span> != <span class="hljs-literal">NULL</span>) &amp;&amp; !<span class="hljs-built_in">strcmp</span>(<span class="hljs-built_in">string</span>, <span class="hljs-string">"ok"</span>);
 }
</code></pre><p>no topo vemos a sinalização que foi gerado pelo git diff e os tais aquivos <strong>A</strong> e <strong>B</strong>, logo após vemos identificações de linhas e conteúdos que devem ser adicionados ou removidos, sendo sinalizados por <strong>-</strong> e <strong>+</strong>. Não necessariamente um patch precisa ser e apenas um arquivo, como vocês podem ver, na primeira linha há uma identificação do nome dos arquivos que foram modificados, logo abaixo poderia ter a identificação de mais dois arquivos por exemplo e suas mudanças para transformar um no outro.</p>
<h1 id="heading-o-que-um-patch-tem-a-ver-com-um-plugin-do-strapi">O que um patch tem a ver com um plugin do Strapi?</h1>
<p>O plugin de versionamento de conteúdo que comentei é esse <a target="_blank" href="https://market.strapi.io/plugins/@notum-cz-strapi-plugin-content-versioning">aqui</a>, cumpre bem seu papel mesmo tendo algumas limitações. Se vocês forem nesse link, vão ver que que tem uma parte da documentação chamada <strong>"Override Save Button (Optional)"</strong>, que basicamente explica como sobrescrever o comportamento do botão padrão de save do Strapi, para toda vez que tiver um novo save ele automaticamente criar uma nova versão e assim ter sucesso no versionamento. E ai você me pergunta: como ele sobrescreve isso? </p>
<p><strong>Usando um patch</strong></p>
<p>ele basicamente sobrescreve o componente do Strapi que faz esse funcionamento, como podemos ver na identificação no topo do arquivo que fala o que está sendo mudado</p>
<p><strong>diff --git a/node_modules/@strapi/admin/admin/src/content manager/components/CollectionTypeFormWrapper/index.js b/node_modules/@strapi/admin/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js</strong></p>
<p>não é muito bom de ler, mas vemos que ele acessa o componente <strong>CollectionTypeFormWrapper</strong> dentro da node modules e ai sim começa a fazer as mudanças necessárias, colocando o comportamento desejado. Para aplicar esse patch ele usa uma lib chamada patch-package.</p>
<h1 id="heading-sobrescrever-algo-dessa-maneira-e-bom">Sobrescrever algo dessa maneira é bom?</h1>
<p>Bem....
O Strapi continua funcionando perfeitamente com o comportamento desejado e o nosso problema foi resolvido! Já que é algo tão pontual e que não interfere em nada no resto, acredito que não há problema nenhum. A própria dashboard do strapi permite algumas customizações, mas nada a esse nível. Já que o patch resolveu o problema, todo mundo sai feliz.</p>
<h1 id="heading-conclusao">Conclusão</h1>
<p>Eu cai nisso de paraquedas, tanto que só fui ver isso a fundo por que o patch que tem na documentação simplesmente parou de funcionar na versão 4.1.7 que é a que estou usando (o componente teve algumas mudanças, por isso o patch quebrou). Com isso, fui procurar e vi que tinha uma <a target="_blank" href="https://github.com/notum-cz/strapi-plugin-content-versioning/issues/21">issue</a> aberta no Github, como não tinha solução ainda, fui lá e adaptei o patch para funcionar na versão 4.1.17 e abri um <a target="_blank" href="https://github.com/notum-cz/strapi-plugin-content-versioning/pull/22">pull request</a> para atualizarem na documentação. Foi uma experiência legal. </p>
]]></content:encoded></item><item><title><![CDATA[Como refatorei testes do Cypress utilizando comandos customizados]]></title><description><![CDATA[Desde o final do ano passado o time em que participo no meu atual emprego vem se alinhando bastante em algumas ideias para a melhoria do nosso produto, e…]]></description><link>https://grossis.hashnode.dev/como-refatorei-testes-do-cypress-utilizando-comandos-customizados</link><guid isPermaLink="true">https://grossis.hashnode.dev/como-refatorei-testes-do-cypress-utilizando-comandos-customizados</guid><category><![CDATA[Cypress]]></category><category><![CDATA[Testing]]></category><category><![CDATA[test]]></category><category><![CDATA[integration]]></category><dc:creator><![CDATA[Caio Grossi]]></dc:creator><pubDate>Wed, 02 Mar 2022 21:40:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/NoNtt7qVGz4/upload/v1646257312908/hmC72Pr5P.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-motivacao">Motivação</h1>
<p>Desde o final do ano passado o time em que participo no meu atual emprego vem se alinhando bastante em algumas ideias para a melhoria do nosso <a target="_blank" href="https://www.passagenspromo.com.br/">produto</a>, e uma delas é a implementação de testes em todo o nosso sistema, desde o Backend até o Frontend, com testes unitários e de integração. Como isso já vem desde o ano passado, já tínhamos uma codebase de testes de integração, fazendo o fluxo de compra completo (pesquisar, selecionar um voo, preencher dados dos passageiros, dados de pagamento e finalizar compra). Tudo isso usando <a target="_blank" href="https://www.cypress.io/">cypress</a>. Foi me passada uma tarefa de criar mais testes, dessa vez para testar especificamente o que chamamos de "frame de pesquisa", que é onde nossos clientes fazem a pesquisa dos voos na home do site. Com isso, acabei me encontrando pela primeira vez com essa codebase que já tínhamos, estava bem estruturada até, mas tinham algumas coisas claras que poderíamos mudar para ficar algo mais fácil de codar e organizado. Após essas mudanças que fiz, venho mostrar para vocês o motivo pelo qual fiz isso e como</p>
<h1 id="heading-adicao-de-typescript-e-estrutura-atual-de-comandos">Adição de Typescript e estrutura atual de comandos</h1>
<p>Sim, o projeto não estava em typescript. Foi a primeira coisa que fiz, instalar e configurar o typescript para ai sim começar a refatoração. Essa parte foi bem simples, sem mistério (se não conhece um jeito de fazer isso, consulte a <a target="_blank" href="https://docs.cypress.io/guides/tooling/typescript-support">documentação</a> oficial). Após isso, parti para a reorganização dos comandos específicos de rotinas que já existiam (pesquisa, preenchimento de dados de passageiros, pagamento e etc). A pasta estava organizada da seguinte maneira:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646250319394/QNh25KLUc.png" alt="image.png" /></p>
<p>Sendo:</p>
<ul>
<li><p><strong>commands.js</strong> o arquivo que em teoria deveria ter comandos personalizados do cypress, mas não estava sendo utilizado;</p>
</li>
<li><p><strong>common.js</strong> contendo funções selecionar um voo, preencher dados de passageiros e etc. (o que eu não considero "common");</p>
</li>
<li><p><strong>desktop-workflow.js</strong> contendo funções de praticamente todo o fluxo de uma compra feita no desktop;</p>
</li>
<li><p><strong>index.js</strong> que em teoria deveria importar todos os comandos personalizados para serem usados sem a necessidade e importar funções nos seus arquivos de teste;</p>
</li>
<li><p><strong>mobile-workflow.js</strong> mesmo que o desktop-workflow.js mas para mobile;</p>
</li>
<li><p><strong>utils.js</strong> contendo todo tipo de função utilitária que possa imaginar, desde formatação de datas, até escolhas randômicas de dados de passageiros para preencher e finalizar a compra.</p>
</li>
</ul>
<p>Para mim, tinham 3 problemas:</p>
<ul>
<li><p>Não utilização dos <a target="_blank" href="https://docs.cypress.io/api/cypress-api/custom-commands">comandos personalizados</a> que o cypress oferece. Se tínhamos um fluxo tão bem definido, por que não colocar funções como selecionar um voo ou preencher dados de um passageiro como algo que poderia ser usado pelo proprio cypress? Do mesmo jeito que usamos o <strong>cy.get()</strong> poderiamos usar um <strong>cy.selectFlight()</strong>. Essas funções já existiam, mas estavam sendo importadas nos nossos arquivos de teste e sendo chamadas como funções simples;</p>
</li>
<li><p>Junção de muitas funções em um escopo abrangente. Bom, o arquivo <strong>desktop-workflow.js</strong> continha todo o fluxo que um cliente faria para comprar em nosso site, desde a pesquisa até a finalização da compra. Se a gente parar pra pensar, isso já está separando em um escopo, sendo dividido entre <strong>desktop</strong> ou <strong>mobile</strong>. Já é alguma separação, mas dá para melhorar. Como em qualquer e-commerce, você tem um fluxo muito bem definido sendo ele:</p>
</li>
</ul>
<p>Pesquisa &gt; Selecionar Produto &gt; Preencher dados &gt; Finalizar Compra</p>
<p>Com esse fluxo bem definido, temos uma nova separação de escopo para os nossos comandos. Ao fazer essa mudança, em vez de termos apenas essas duas separações entre mobile e desktop, a nível de código, teríamos uma separação de rotinas entre: pesquisa, seleção de produto, preenchimento e finalização de compra. Particularmente, acredito que com essa estruturação, fica mais fácil de entender o projeto e dar manutenção.</p>
<ul>
<li>Arquivo de utils muito grande. Como disse, o arquivo de utils levava todas as funções possíveis. Foi bem simples minha mudança, apenas separei ele em escopos menores, no caso dois: datas e dados de itinerário. Sendo dados de itinerário tudo que diz respeito a geração de dados randômicos para pesquisa, passageiro, pagamentos e etc.</li>
</ul>
<p>Após identificar isso, fui implementar as mudanças.</p>
<h1 id="heading-nova-estrutura-de-comandos">Nova estrutura de comandos</h1>
<p>Após minha pequena refatoração, a estrutura da pasta ficou assim:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646252701003/D_aJEmFan.png" alt="image.png" /></p>
<p>Como podem ver, "resolvi" os três pontos principais que na minha concepção eram problemas. Talvez o que mais chamou sua atenção foram esses arquivos de tipagem, mas vou explicar o motivo deles ali.</p>
<h2 id="heading-como-eram-feitos-os-comandos-antigos">Como eram feitos os comandos antigos</h2>
<p>Antes os comandos eram funções simples do javascript, como esse abaixo: </p>
<pre><code>export const fulfillContact <span class="hljs-operator">=</span> (email) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
  cy.get(<span class="hljs-string">'[name=name]'</span>).type(randomData(<span class="hljs-string">'names'</span>))
  cy.get(<span class="hljs-string">'[name=email]'</span>).type(email)
  cy.get(<span class="hljs-string">'#confirmation'</span>).type(email)
  cy.get(<span class="hljs-string">'[name=phone]'</span>).type(randomData(<span class="hljs-string">'phones'</span>))
}
</code></pre><p>isso acarretava em ter que importar essa função dentro do arquivo de teste para ai sim poder utiliza-lá e ainda assim "às cegas" por ser javascript.</p>
<h2 id="heading-como-sao-feitos-os-comandos-novos">Como são feitos os comandos novos</h2>
<p>Agora eles são feitos usando o <strong>Cypress.Commands.add()</strong>, que deixa eles nativos no cypress, podendo ser chamados através do <strong>cy</strong>:</p>
<pre><code>Cypress.Commands.add(<span class="hljs-string">'fulfillContact'</span>, (email: <span class="hljs-keyword">string</span>) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
  cy.get(<span class="hljs-string">'[name=name]'</span>).type(randomData(<span class="hljs-string">'names'</span>))
  cy.get(<span class="hljs-string">'[name=email]'</span>).type(email)
  cy.get(<span class="hljs-string">'#confirmation'</span>).type(email)
  cy.get(<span class="hljs-string">'[name=phone]'</span>).type(randomData(<span class="hljs-string">'phones'</span>))
})
</code></pre><p>já que agora ele é um comando customizado do cypress, podendo ser acessado direto do <strong>cy</strong> e estamos utilizando typescript, nada mais justo do que fazer uma tipagem personalizada, correto ? Pois aqui está o nosso <strong>types.d.ts</strong>:</p>
<pre><code>// <span class="hljs-keyword">load</span> <span class="hljs-keyword">type</span> definitions <span class="hljs-keyword">from</span> Cypress <span class="hljs-keyword">module</span>
/// &lt;<span class="hljs-keyword">reference</span> types=<span class="hljs-string">"cypress"</span> /&gt;

<span class="hljs-keyword">declare</span> namespace Cypress {
    <span class="hljs-keyword">interface</span> Chainable {

      <span class="hljs-comment">/**
      * Comando customizado para preencher campos de contato
      * @example cy.fulfillContact(caiogrossi6@gmail.com)
      */</span>
      fulfillContact(email: <span class="hljs-keyword">string</span>): Chainable&lt;<span class="hljs-keyword">Element</span>&gt;
    }
}
</code></pre><p>com isso, alem de termos o auto-complete usando o <strong>cy</strong>, ainda teremos um pop-up do vscode escrito exatamente essa descrição que colocamos, uma espetáculo, não?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646255718691/TB6c1jFUe.png" alt="Captura de tela de 2022-03-02 18-13-56.png" /></p>
<h1 id="heading-consideracoes-finais">Considerações finais</h1>
<p>Essa foi minha primeira aventura refatorando algo real de um produto real, mesmo não sendo uma refatoração grande e complexa, acredito que contribuiu para a legibilidade. 
Sempre prezei ao máximo pela organização do código e também de usar todas as features que estiverem disponíveis para facilitar o trabalho. Acredito que consegui alcançar isso com essa refatoração. </p>
]]></content:encoded></item><item><title><![CDATA[Conhecendo o framework NestJS]]></title><description><![CDATA[Se você é da área de desenvolvimento, provavelmente já ouviu falar do NestJS (Não NextJS!) em alguma propaganda do Youtube ou até mesmo em outros posts…]]></description><link>https://grossis.hashnode.dev/conhecendo-o-framework-nestjs</link><guid isPermaLink="true">https://grossis.hashnode.dev/conhecendo-o-framework-nestjs</guid><category><![CDATA[nest]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Caio Grossi]]></dc:creator><pubDate>Fri, 28 May 2021 18:59:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1622228356102/gky0mCvBQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="apresentacao-do-framework">Apresentação do framework</h2>
<p>Se você é da área de desenvolvimento, provavelmente já ouviu falar do  <a target="_blank" href="https://nestjs.com/">NestJS</a>  (Não NextJS!) em alguma propaganda do Youtube ou até mesmo em outros posts. Esse framework, que usa NodeJS por baixo dos panos e é fortemente inspirado no Angular, tem como principal motivação a proposta de uma arquitetura forte que permite a criação de aplicações escaláveis, fracamente acopladas e de fácil manutenção e teste. Como ele faz isso? Descobriremos nesse post !</p>
<h2 id="principais-pilares">Principais pilares</h2>
<p>Das inúmeras funcionalidades que o NestJS te oferece, vou destacar as principais que serão utilizadas em praticamente todo projeto.</p>
<h3 id="modularizacao">Modularização</h3>
<p>Você provavelmente já ouviu esse termo em algum lugar, que nada mais é do que o próprio nome diz: separar a sua aplicação em módulos. Com essa prática, podemos separar as responsabilidades da nossa aplicação em diferentes módulos que depois podemos "conecta-los" para o compartilhamento de funcionalidades. O NestJS leva a modularização <strong>MUITO</strong> a sério, como pode ser visto nesse trecho da sua própria documentação: </p>
<blockquote>
<p>Nós queremos enfatizar que módulos são fortemente recomendados como uma efetiva maneira de organizar seus componentes. Portanto, para a maiora das aplicações, a arquitetura resultante será de múltiplos módulos, cada um encapsulando uma série de funcionalidades.</p>
</blockquote>
<p>Sem dúvidas, é um bom jeito de separar as responsabilidades da sua aplicação, deixando mais fácil uma futura manutenção e testes. Se você nem imagina como se parece um módulo do Nest, aqui está um exemplo simples:</p>
<pre><code><span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { CatsController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.controller'</span>;
<span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;
<span class="hljs-keyword">import</span> { DogsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'../dogs/dogs.module'</span>

@Module({
  imports: [DogsModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CatsModule</span> {}</span>
</code></pre><p>Como no exemplo, todo módulo que você criar deve ter esse decorator "Module" que serve para o Nest saber que é um módulo e iniciar sua aplicação. Dentro do objeto que esse decorator "Module" recebe como argumento, é definido o que ele pode importar (normalmente outro módulo), controllers (o que veremos mais tarde), providers (que veremos mais tarde) e o que ele pode exportar (normalmente um serviço). No final, exportamos uma classe com o nome do nosso módulo.</p>
<p>Em um grande projeto onde hajam muitos módulos, fica chato toda hora ter que escrever tudo isso para a criação. Por isso, a  <a target="_blank" href="https://docs.nestjs.com/cli/overview">Nest CLI</a> resolve esse problema para nós, criando toda a estrutura de um módulo, controllers e services passando somente o nome que desejamos. Para saber mais sobre módulos, visite a  <a target="_blank" href="https://docs.nestjs.com/modules">documentação oficial</a>.</p>
<h3 id="controllers">Controllers</h3>
<p>Os famosos controlles, são responsáveis por lidarem com as requisições do client e retornar suas devidas repostas. Um exemplo simples de controller no Nest pode ser esse:</p>
<pre><code><span class="hljs-keyword">import</span> { Controller, Get } from <span class="hljs-string">'@nestjs/common'</span>;

<span class="hljs-meta">@Controller(<span class="hljs-meta-string">'cats'</span>)</span>
export <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CatsController</span> </span>{
  <span class="hljs-meta">@Get(<span class="hljs-meta-string">'index'</span>)</span>
  findAll(): string {
    <span class="hljs-keyword">return</span> <span class="hljs-string">'This action returns all cats'</span>;
  }
}
</code></pre><p>Para criarmos esse simples controller precisamos importar um decorator "Controller" e "Get" (parecido com o que fizemos para o módulo). O decorator de "Controller" como o próprio nome já diz, serve para dizer ao Nest que isso será um controller. O "Get" serve para dizer qual será o método http da rota desse controller.</p>
<p>Você desse ter percebido que passamos como argumento a string "cats" para o decorator de controller, isso serve para definir a rota que esse controller irá lidar. Todas as requisições que tenham a url por exemplo "http://localhost:3000/cats" irão cair nesse controller. A mesma coisa serve para o argumento que passamos para o decorator do método http, no caso "index." Isso quer dizer que uma requisição com a rota "http://localhost:3000/cats/index" irá ser tratada pela função <strong>findAll()</strong>, que no momento apenas nos retorna uma string. Assim que é feito o roteamento no NestJs, interessante né?</p>
<p>Claro que isso que estou apresentando é o básico dos controllers, para contruir algo real você precisará de um pouco mais que isso, como receber os dados da requisição como parâmetro da função que você definir para lidar com "X" rota, ou definir quais rotas da sua aplicação serão protegidas ou não. Para saber mais sobre controllers no Nest, visite a  <a target="_blank" href="https://docs.nestjs.com/controllers">documentação oficial</a>.</p>
<h3 id="providers">Providers</h3>
<p>Providers são conceitos importantíssimos do Nest, já que neles que ficam praticamente toda a lógica da sua aplicação. A ideia principal dos providers é trabalhar com injeção de dependências, podendo compartilhar por toda a aplicação funcionalidades de diferentes módulos. O principal provider que veremos são os "serviços". Vamos criar um simples provider (serviço), ficará algo assim:</p>
<pre><code><span class="hljs-keyword">import</span> { Injectable } from <span class="hljs-string">'@nestjs/common'</span>;

<span class="hljs-meta">@Injectable()</span>
export <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CatsService</span> </span>{

  sayMeow() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Meow"</span>;
  }

  scratch() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Scratched!"</span>;
  }
}
</code></pre><p>claro que esse exemplo é muito simples, mas é somente para mostrar que a lógica, as regras de negócio devem ser delegadas para o serviço. Um exemplo mais elaborado seria um serviço para lidar com a rota que criamos no nosso controller. Nós tinhamos uma rota "cats/index", correto? Essa rota com certeza seria tratada por uma função do nosso "CatsService", como por exemplo:</p>
<pre><code><span class="hljs-keyword">import</span> {  Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { InjectRepository } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
<span class="hljs-keyword">import</span> { Repository } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;
<span class="hljs-keyword">import</span> { Cat } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cat.entity'</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-meta">@InjectRepository</span>(Cat)
    <span class="hljs-keyword">private</span> catsRepository: Repository&lt;Cat&gt;
  </span>) {}

  <span class="hljs-keyword">async</span> findAll() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.catsRepository.findAll();
  }
</code></pre><p>esse exemplo mesmo continuando simples, já é algo funcional. Apenas injetamos um repositório de Cat que foi feito com TypeORM no construtor da classe (não vou abordar a configuração de repositórios TypeORM neste post, se quiser ver mais sobre isso acesse a  <a target="_blank" href="https://docs.nestjs.com/techniques/database">documentação oficial</a>. Apenas tenha em mente que estou injetando um certo repositório TypeORM nesse serviço para usar suas funcionalidades.) e declaramos uma função "findAll" que nos retorna todos os items desse repositório. Ainda um simples exemplo de serviço, mas funcional.</p>
<h3 id="injecao-de-dependencias">Injeção de dependências</h3>
<p>Por último, mas não menos importante, a famosa injeção de dependêcias. Na última sessão você deve ter percebido que antes de declararmos nossa classe de serviço, colocamos um decorator "Injectable", isso quer dizer que nossa classe aceita a injeção de dependências. Isso que estamos fazendo, injetando o repostório por meio do construtor da classe, voilà! Por enquanto apenas injetamos um respositório, mas podemos injetar serviços em outros serviços também, que é o mais comum! Supondo que temos o módulo "dog", que se parece com isso: </p>
<pre><code><span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { DogsController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dogs.controller'</span>;
<span class="hljs-keyword">import</span> { DogsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dogs.service'</span>;

@Module({
  controllers: [DogsController],
  providers: [DogsService],
  exports: [DogsService]
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DogsModule</span> {}</span>
</code></pre><p>você já sabe o que isso significa, mas preste atenção que estamos exportando o serviço desse módulo. Mas como importamos ele em outro módulo? Simples assim:</p>
<pre><code><span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { CatsController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.controller'</span>;
<span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;
<span class="hljs-keyword">import</span> { DogsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'../dogs/dogs.module'</span>

@Module({
  imports: [DogsModule],
  controllers: [CatsController],
  providers: [CatsService],
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CatsModule</span> {}</span>
</code></pre><p>preste atenção que estamos importando o "DogsModule", ou seja, no "CatsModule" temos acesso a tudo que o "DogsModule" exportou! Vou dar um exemplo de como injetar o serviço de "dogs" no serviço de "cats":</p>
<pre><code><span class="hljs-keyword">import</span> {  Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { InjectRepository } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
<span class="hljs-keyword">import</span> { Repository } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;
<span class="hljs-keyword">import</span> { Cat } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cat.entity'</span>;
<span class="hljs-keyword">import</span> { DogsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../dogs/dogs.service'</span>

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-meta">@InjectRepository</span>(Cat)
    <span class="hljs-keyword">private</span> catsRepository: Repository&lt;Cat&gt;,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> dogsService: DogsService,
  </span>) {}

  <span class="hljs-keyword">async</span> findAll() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.catsRepository.findAll();
  }
</code></pre><p>aqui não estamos usando nenhuma função do serviço "dogs", mas poderiamos usar facilmente dentro de qualquer função com "this.dogsService.nomeDaFunção()".</p>
<p>Outro exemplo importande disso é quando queremos usar uma função do serviço dentro do controller, que é muito feito. Um exemplo:</p>
<pre><code><span class="hljs-keyword">import</span> { Controller, Get } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;

<span class="hljs-meta">@Controller</span>(<span class="hljs-string">'cats'</span>)
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsController {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> catsService: CatsService</span>) {}

  <span class="hljs-meta">@Get</span>(<span class="hljs-string">'index'</span>)
  <span class="hljs-keyword">async</span> findAll(): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.catsService.findAll();
  }
}
</code></pre><p>aqui injetamos o serviço do próprio módulo para poder usar nas rotas do controller, passando coisas mais complexas para o nosso serviço lidar. Basicamente, isso é injeção de dependências no NestJs!</p>
<h2 id="consideracoes-finais">Considerações finais</h2>
<p>Com esse post espero ter ajudado você a entender as funcionalidades básicas do Nest e de como ele se comportar e talvez até motivado a testar esse framework. Se você veio do Angular, será bem mais fácil de aprender ele já que é fortemente inspirado (a sintaxe por exemplo). Caso você queira ver um exemplo de aplicação construid em Nest, tem esse meu  <a target="_blank" href="https://github.com/CaioGrossi/Shoppingify-API">projeto</a> que foi o que me motivou a fazer esse post. A documentação oficial do Nest pode ser encontrada  <a target="_blank" href="https://docs.nestjs.com/">aqui</a>, onde há tudo que você precisa saber para construir aplicações reais. </p>
<p>Até a próxima ! </p>
]]></content:encoded></item><item><title><![CDATA[Criando e testando um contexto com React,Typescript, Jest e React-Testing-Library]]></title><description><![CDATA[Estava no meio de um projeto pessoal que com certeza vai ser um dos maiores e que me deu mais trabalho. Nesse projeto, precisei criar um context para…]]></description><link>https://grossis.hashnode.dev/criando-e-testando-um-contexto-com-reacttypescript-jest-e-react-testing-library</link><guid isPermaLink="true">https://grossis.hashnode.dev/criando-e-testando-um-contexto-com-reacttypescript-jest-e-react-testing-library</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Jest]]></category><dc:creator><![CDATA[Caio Grossi]]></dc:creator><pubDate>Wed, 28 Apr 2021 22:16:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1625260165693/-A2HpzAPP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="motivacao">Motivação</h1>
<p>Estava no meio de um projeto pessoal que com certeza vai ser um dos maiores e que me deu mais trabalho. Nesse projeto, precisei criar um context para guardar os itens que o usuário colocaria no carrinho. Criar um contexto muita gente já sabe, por isso aqui vou mostrar como criar e testar um, que é um a mais que pode facilitar muito sua vida no futuro.</p>
<h1 id="tipando-o-contexto">Tipando o  contexto</h1>
<p>Nesse ponto já presumo que você já tem todo um ambiente de desenvolvimento configurado e o projeto em andamento (em React, claro).</p>
<p>Primeiro, crie uma pasta nomeada 'hooks' dentro 'src' e outra pasta dentro dela com o nome do contexto que você vai criar e um aquivo 'index.tsx', o meu ficou assim: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619641952256/l_uWhe8Bq.png" alt="hoojs.png" /></p>
<p>depois disso podemos começar a tipagem.</p>
<p>Para criarmos um contexto e usa-lo como um hook, vamos precisar das seguintes importações no nosso arquivo 'index.tsx' que acabamos de criar.</p>
<pre><code><span class="hljs-keyword">import</span> React, { createContext, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
</code></pre><p>Se você estiver usando Typescript (o que eu espero que sim por que facilita muito a vida) precisaremos criar dois tipos:</p>
<ul>
<li>O tipo das propriedades que o nosso <strong>PROVIDER</strong> vai receber;</li>
<li>O tipo das propriedades que ficarão armazenadas no nosso contexto, ou seja, as informações que desejamos compartilhar entre os componentes;</li>
</ul>
<p>Primeiro vamos ao tipo do provider que é o mais simples, o seu deve se parece com algo assim: </p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">ShoppingListProviderProps</span></span> = {
  children: React.ReactNode;
};
</code></pre><p>o meu contexto se chama "ShoppingList", substitua no seu projeto pelo nome do seu contexto para fazer sentido. A propriedade não muda, nosso provider recebe um 'children', ou seja, todos os componentes que tiverem nesse children terão acesso as informações do nosso contexto.</p>
<p>O nosso proximo tipo é o dos dados que o nosso contexto vai armazenar/compartilhar, o meu se parece com algo assim: </p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ListItem = {
  id: <span class="hljs-built_in">string</span>;
  name: <span class="hljs-built_in">string</span>;
  quantity: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ShoppingListContextData = {
  items: ListItem[];
  onAdd: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span>, name: <span class="hljs-built_in">string</span>, quantity: <span class="hljs-built_in">number</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  onIncrease: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  onRemove: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  onDecrease: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  isInList: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">boolean</span>;
};
</code></pre><p>meu contexto tem um dado 'items' (onde ficará armazenado os itens que meu usuário colocar no carrinho), onAdd(responsável por adicionar os itens ao meu carrinho), onIncrease(aumenta a quatidade de certo item no meu carrinho), onDecrease(diminui a quantidade de certo item no meu carrinho) e inList(confere se um item pertecence ou não ao meu carrinho). Lembrando que essa tipagem faz sentido para as propriedades do <strong>MEU CONTEXTO</strong>, tipe as propriedades de acordo com o seu. Agora que já temos a nossa tipagem, vamos criar o contexto.</p>
<h1 id="criando-o-contexto">Criando o contexto</h1>
<p>Agora vamos criar o nosso contexto usando as nossas importações, ficará algo assim: </p>
<pre><code><span class="hljs-keyword">const</span> ShoppingListContext = createContext&lt;ShoppingListContextData&gt;(
  ShoppingListContextDefaultValues
);
</code></pre><p>repare que eu tipo as propriedades com o 'type' que criamos no bloco anterior e que coloco como valor inicial um 'ShoppingListContextDefaultValues' que se parece com isso: </p>
<pre><code>const ShoppingListContextDefaultValues = {
  items: [],
  onAdd: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-literal">null</span>,
  onIncrease: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-literal">null</span>,
  onRemove: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-literal">null</span>,
  onDecrease: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-literal">null</span>,
  isInList: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-literal">false</span>
};
</code></pre><p>apenas coloco valores padrões que não vão fazer diferença na prática, faço isso por que o hook createContext pede valores iniciais. Se você nao quiser criar esse objeto, pode fazer assim também: </p>
<pre><code><span class="hljs-keyword">const</span> ShoppingListContext = createContext(
  {} <span class="hljs-keyword">as</span> ShoppingListContextData
);
</code></pre><p>com isso, já criamos o nosso contexto e agora devemos criar as propriedades que ele possui.</p>
<h1 id="criando-as-propriedades-do-contexto">Criando as propriedades do contexto</h1>
<p>Quando criamos um contexto em React, ele tem uma propriedade chamada 'Provider' que pode ser chamada como um component react, algo assim: </p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">ShoppingListContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> }}&gt;</span>
      {children}
<span class="hljs-tag">&lt;/<span class="hljs-name">ShoppingListContext.Provider</span>&gt;</span>
</code></pre><p>como já disse antes, todos os componentes que estiverem dentro de 'children' vão poder acessar as propriedades, o 'value' que devemos passar são os valores/propriedades em sí. Por isso, vamos começar a criar as nossas propriedades.</p>
<p>Como eu armazeno itens no meu contexto, vou precisar do hook useState que vou importar no meu arquivo ficando assim: </p>
<pre><code><span class="hljs-keyword">import</span> React, { createContext, useContext, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
</code></pre><p>As minhas propriedades vão ficar assim: </p>
<h2 id="items">items</h2>
<pre><code><span class="hljs-keyword">const</span> [items, setItems] = useState&lt;ListItem[]&gt;([]);
</code></pre><h2 id="onadd">onAdd</h2>
<pre><code><span class="hljs-keyword">const</span> onAdd = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span>, name: <span class="hljs-built_in">string</span>, quantity: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
    setItems([...items, { id, name, quantity }]);
  };
</code></pre><h2 id="onremove">onRemove</h2>
<pre><code><span class="hljs-keyword">const</span> onRemove = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> filteredItems = items.filter(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item.id !== id);
    setItems(filteredItems);
  };
</code></pre><h2 id="onincrease">onIncrease</h2>
<pre><code> <span class="hljs-keyword">const</span> onIncrease = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> itemsArray = [...items];

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; itemsArray.length; i++) {
      <span class="hljs-keyword">const</span> item = itemsArray[i];

      <span class="hljs-keyword">if</span> (item.id == id) {
        item.quantity++;
        <span class="hljs-keyword">break</span>;
      }
    }

    setItems(itemsArray);
  };
</code></pre><h2 id="ondecrease">onDecrease</h2>
<pre><code><span class="hljs-keyword">const</span> onDecrease = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> itemsArray = [...items];

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; itemsArray.length; i++) {
      <span class="hljs-keyword">const</span> item = itemsArray[i];

      <span class="hljs-keyword">if</span> (item.id == id) {
        item.quantity--;
        <span class="hljs-keyword">break</span>;
      }
    }

    setItems(itemsArray);
  };
</code></pre><h2 id="isinlist">isInList</h2>
<pre><code>const isInList = <span class="hljs-function"><span class="hljs-params">(id: string)</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> items.some(<span class="hljs-function"><span class="hljs-params">(item)</span> =&gt;</span> item.id === id);
  };
</code></pre><p>não vou entrar muito em detalhes de como cada função funciona, mas o importante é saber o que cada uma faz que já descrevi anteriormente.</p>
<p>Depois de criar as propriedades, precisamos criar um componente de provider que vai conter as nossa propriedades e no final retornanar um <strong>ShoppingList.Provider</strong> com o nosso children e as propriedades como no parâmetro value como já mostrado. Fazemos isso para guardar tudo que está relacionado ao contexto dentro do seu arquivo. No final teremos algo assim: </p>
<pre><code><span class="hljs-keyword">const</span> ShoppingListProvider = <span class="hljs-function">(<span class="hljs-params">{ children }: ShoppingListProviderProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [items, setItems] = useState&lt;ListItem[]&gt;([]);

  <span class="hljs-keyword">const</span> onAdd = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span>, name: <span class="hljs-built_in">string</span>, quantity: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
    setItems([...items, { id, name, quantity }]);
  };

  <span class="hljs-keyword">const</span> onRemove = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> filteredItems = items.filter(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item.id !== id);
    setItems(filteredItems);
  };

  <span class="hljs-keyword">const</span> onIncrease = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> itemsArray = [...items];

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; itemsArray.length; i++) {
      <span class="hljs-keyword">const</span> item = itemsArray[i];

      <span class="hljs-keyword">if</span> (item.id == id) {
        item.quantity++;
        <span class="hljs-keyword">break</span>;
      }
    }

    setItems(itemsArray);
  };

  <span class="hljs-keyword">const</span> onDecrease = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> itemsArray = [...items];

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; itemsArray.length; i++) {
      <span class="hljs-keyword">const</span> item = itemsArray[i];

      <span class="hljs-keyword">if</span> (item.id == id) {
        item.quantity--;
        <span class="hljs-keyword">break</span>;
      }
    }

    setItems(itemsArray);
  };

  <span class="hljs-keyword">const</span> isInList = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> items.some(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item.id === id);
  };

  <span class="hljs-keyword">return</span> (
    &lt;ShoppingListContext.Provider
      value={{ items, onAdd, onDecrease, onRemove, onIncrease, isInList }}
    &gt;
      {children}
    &lt;/ShoppingListContext.Provider&gt;
  );
};
</code></pre><p>esse nosso componente será responsavel por ser o nosso provider. Esse component 'ShoppingListProvider' nós podemos importa-lo em qualquer lugar do nosso arquivo para definir quais componentes vão poder acessar essas propriedades. Antes disso, vamos criar um hook customizado que ficará assim: </p>
<pre><code>const useShoppingList = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> useContext(ShoppingListContext);
</code></pre><p>esse hook é apenas para facilitar a utlização do nosso contexto, apenas uma função que retorna o nosso contexto.</p>
<p>Um exemplo de comos podemos utilizar esse 'ShoppingListProvider' é assim: </p>
<pre><code><span class="hljs-keyword">import</span> { ShoppingListProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'hooks/use-shoppinglist'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ShoppingListProvider</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ComponentesDaHome</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ShoppingListProvider</span>&gt;</span></span>
  );
}
</code></pre><p>Esse componente 'Home' como o nome já diz é o meu componte da página Home, ao envolver o retorno dele como o meu provider, <strong>TODOS</strong> os componentes que estiverem dentro dele podem acessar as propriedades, pelo nosso hook personalizado 'useShoppingList' desse jeito: </p>
<pre><code><span class="hljs-keyword">const</span> {
    isInList,
    items,
    onAdd,
    onDecrease,
    onIncrease,
    onRemove
  } = useShoppingList();
</code></pre><p>e claro, precisamos importa-lo no nosso arquivo. Agora que já temos um contexto totalmente funcional, precisamos testa-lo que é o que vamos fazer no próximo bloco.</p>
<h1 id="testando-o-nosso-contexto">Testando o nosso contexto</h1>
<p>Talvez você não esteja acostumado com testes em cotextos/hooks (como eu não estava), mas por ser uma parte importantíssima do nosso projeto ele pode e <strong>DEVE</strong> ser testado. Como praticamente todo os projetos React já vem com o jest e a react testing library instalados (que é o que vamos usar) vou presumir que você já tem isso configurado, se não tiver vá ate o  <a target="_blank" href="https://testing-library.com/docs/react-testing-library/intro">link da testing library</a> para configurar seu ambiente e ao do  <a target="_blank" href="https://jestjs.io/pt-BR/docs/getting-started">jest</a>. Com isso podemos começar.</p>
<h2 id="instalando-pacote">Instalando pacote</h2>
<p>Como vamos testar um hook, precisamos de um pacote extra da testing library que fará isso para gente, por isso instale o pacote testing-library/react-hooks: </p>
<pre><code>npm install --save-dev <span class="hljs-meta">@testing</span>-<span class="hljs-keyword">library</span>/react-hooks

ou

yarn add <span class="hljs-meta">@testing</span>-<span class="hljs-keyword">library</span>/react-hooks -D
</code></pre><p>após isso, crie um arquivo de teste dentro do diretório do seu contexto.</p>
<p>No nosso arquivo de teste vamos precisar importar o nosso provider, nosso hook personalizado, a função 'renderHook' do pacote que acabamos de instalar e uma função helper do proprio react para testes , ficará assim: </p>
<pre><code><span class="hljs-keyword">import</span> {
  useShoppingList,
  ShoppingListProvider,
  ShoppingListProviderProps
} <span class="hljs-keyword">from</span> <span class="hljs-string">'.'</span>;

<span class="hljs-keyword">import</span> { renderHook } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react-hooks'</span>;
<span class="hljs-keyword">import</span> { act } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/test-utils</span>
</code></pre><p>após isso já podemos abrir o nosso bloco describe para testes e iniciar. Para testar o nosso hook, precisamos ver o que necessita de testes (no meu caso são as 5 funções) e aí sim começar. Com isso, vou testar primeiro a função de adicionar um item que deve ficar assim: </p>
<pre><code>describe(<span class="hljs-string">'useShoppinglist'</span>, () =&gt; {
  it(<span class="hljs-string">'should add one item to the list'</span>, () =&gt; {
    const <span class="hljs-keyword">wrapper</span> = ({ children }: ShoppingListProviderProps) =&gt; (
      &lt;ShoppingListProvider&gt;{children}&lt;/ShoppingListProvider&gt;
    );

    const { result } = renderHook(() =&gt; useShoppingList(), {
      <span class="hljs-keyword">wrapper</span>
    });

    act(() =&gt; {
      result.<span class="hljs-keyword">current</span>.onAdd(<span class="hljs-string">'1'</span>, <span class="hljs-string">'cake'</span>, <span class="hljs-number">5</span>);
    });

    expect(result.<span class="hljs-keyword">current</span>.items).toStrictEqual([
      { id: <span class="hljs-string">'1'</span>, <span class="hljs-type">name</span>: <span class="hljs-string">'cake'</span>, quantity: <span class="hljs-number">5</span> }
    ]);
  });
});
</code></pre><p>antes de mais nada precisamos criar um 'wrapper' para definir onde o nosso hook será renderizado, faço isso nas primeiras linhas do bloco 'it' apenas criando uma função e retornando o nosso provider com um children. Após isso, precisamos usar a função 'renderHook' para fazer o nosso hook ficar utilizavel dentro do nosso teste. Essa função recebe como primeiro parâmentro uma função que retorna o nosso hook e como segundo um objeto que deve ter dentro dele o nosso 'wrapper' que por sua vez <strong>TEM</strong> que ter o provider englobando o retorno. </p>
<p>Esse hook nos retorna um atributo 'result' que é onde está todas as nossas propriedades do contexto. Para testarmos a função de 'onAdd' bastar chamar <strong>result.current.onAdd('1', 'cake', 5)</strong> (dentro do 'result' nós temos esse atributo 'current' que se refere ano nosso hook, tanto que podemos acessar todas as nossas propriedades a partir de 'current').  Usamos essa função 'act' importada de 'react-dom/test-utils' por ser recomendada pela própria documentação do react quando fazemos mudança de estados em testes, leia mais <a target="_blank" href="https://reactjs.org/docs/test-utils.html#act">aqui</a>.</p>
<p>Após isso tudo, podemos de fato testar se a nossa função 'onAdd' realmente funcionou chamando a função 'expect', que recebe o que queremos analisar (no caso os 'items' do nosso contexto) e o que queremos fazer, nesse caso eu quero que ele seja estritamente igual a um array que contém um objeto com as propriedades exatamente iguais as que eu adicionei. Não vou testar as outras quatro funções pois vocês já imaginam como dever ser os testes e meu objetivo era mostrar como testas hooks, e não funções específicas do meu contexto.</p>
<h1 id="conclusao">Conclusão</h1>
<p>Após essa gigante dissertação, aprendemos a como criar um contexto com um hook personalizado no React e ainda mais testar tudo isso, o que é de extrema importância. Agora que você já saber fazer ambos, não perca tempo e adicione os testes nos seus hooks personalizados de imediato !!!!</p>
<p>Até a proxima</p>
]]></content:encoded></item><item><title><![CDATA[Como utilizar a API de sugestão de lugares do google com React e Typescript.]]></title><description><![CDATA[Ontem estava quase finalizando um projeto pessoal meu que consiste em consumir uma API de tempo meteorológico e mostrar os dados como temperatura, imagem…]]></description><link>https://grossis.hashnode.dev/como-utilizar-a-api-de-sugestao-de-lugares-do-google-com-react-e-typescript</link><guid isPermaLink="true">https://grossis.hashnode.dev/como-utilizar-a-api-de-sugestao-de-lugares-do-google-com-react-e-typescript</guid><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Google]]></category><category><![CDATA[APIs]]></category><category><![CDATA[google maps]]></category><dc:creator><![CDATA[Caio Grossi]]></dc:creator><pubDate>Tue, 09 Mar 2021 20:01:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1625260288014/8KFGVO3LgF.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="motivacao">Motivação</h1>
<p>Ontem estava quase finalizando um projeto pessoal meu que consiste em consumir uma API de tempo meteorológico e mostrar os dados como temperatura, imagem que remeta ao tempo, pressão do ar, visibilidade e etc. O projeto final pode ser encontrado <a target="_blank" href="https://github.com/CaioGrossi/Weather-App-V2">aqui</a> no meu Github. Não sei vocês, mas quando pesquiso alguma localização em qualquer aplicação o mínimo que eu espero é que sejam sugeridos lugares ao mesmo tempo que digito, que é algo que facilita muito. Com isso procurei como implementar a API de lugares do google no meu projeto (que faz exatamente isso) e cheguei nesse <a target="_blank" href="https://www.npmjs.com/package/use-places-autocomplete">pacote</a> e como não encontrei modos de utiliza-lo além da própria documentação no npm, resolvi escrever esse post como algo complementar.</p>
<h1 id="criando-uma-conta-no-google-cloud-e-ativando-a-api">Criando uma conta no google cloud e ativando a api</h1>
<p>Antes de mais nada, você precisará de uma conta no google e de acesso a API de places. Vá ate o site do <a target="_blank" href="https://cloud.google.com/">google cloud</a> faça login e os primeiros passos(a parte de adicionar um cartão é OBRIGATÓRIA, você terá um saldo de 300 dólares para poder usar o que dá e sobre). Após fazer o login e os primeiros passos, você será redirecionado para o console de sua conta. Já no console da sua conta, provavelmente terá um projeto inicial, se não tiver, crie um clicando do lado do logo da cloud na barra de navegação.</p>
<p>Após a criação do seu projeto, clique em API's na tela inicial (vai ser algo como essa imagem abaixo)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615316857811/35JY6X2K_.png" alt="api_dash.png" /></p>
<p>Clique em "biblioteca" na sidebar esquerda. Pesquise por "Places API" e "Maps Javascript API", ative as duas. Após ativar as duas API's, em "credencias" na mesma sidebar e crie uma credencial. Pronto, você já tem as API's que precisamos ativadas e uma credencial para fazer a consulta. Agora vamos para o código.</p>
<h1 id="adicionando-o-script-do-google-no-nosso-html">Adicionando o script do google no nosso HTML</h1>
<p>Após a criação da sua key e ativação das API's, vamos ao código. Antes de tudo, no seu index.html (se tiver utilizando React), insira a seguinte linha de código no final do body:
<code>&lt;script src="https://maps.googleapis.com/maps/api/js?key=SUA-KEY-AQUI&amp;libraries=places"&gt;&lt;/script&gt;</code>, mas claro substituindo "SUA-KEY-AQUI" para a credencial que criamos na sessão anterior. Após isso, estamos prontos para usar o pacote.</p>
<h1 id="utilizando-o-pacote-use-places-autocomplete">Utilizando o pacote use-places-autocomplete</h1>
<p>Para usarmos o pacote, primeiro vamos instalar com  <code>yarn add use-places-autocomplete</code>, e se tiver usando Typescript adicione <code>yarn add -D @types/googlemaps</code>. Com isso, já temos acesso ao nosso hook. Para importa-lo adicione <code>import usePlacesAutocomplete from 'use-places-autocomplete';</code> no topo de seu projeto.</p>
<p>Agora vamos chamar o hook para usar suas funções, para chamar faça igual a imagem abaixo</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615318055342/GCi6D9uUu.png" alt="hook.png" /></p>
<p>Onde temos:</p>
<ul>
<li>ready: define se está pronta para ser chamada;</li>
<li>status: o status da chamada que fizemos;</li>
<li>value: o valor do input que estamos digitando ;</li>
<li>data: as sugestões dos lugares;</li>
<li>clearSuggestions: limpar as sugestões;</li>
<li>setValue: para mudar o valor do input;</li>
</ul>
<p>Agora vamos para configuração das tags</p>
<h2 id="configurando-o-input-e-criando-funcao-handleinput">Configurando o input e criando função handleInput</h2>
<p>Para configurar o input, coloque as propriedades como está na imagem abaixo:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615318438440/UCEaWJWtR.png" alt="input.png" /></p>
<p>E para função de handleInput:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615318459603/phPYvJhZi.png" alt="handleInput.png" /></p>
<p>Já temos nosso input e a função handleInput configurada, vamos para renderizar as sugestões.</p>
<h2 id="renderizando-as-sugestoes">Renderizando as sugestões</h2>
<p>Para renderizar as sugestões, precisamos usar o status que resgatamos do nosso hook. Fazemos desse jeito:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615318656347/1HAdqt6DG.png" alt="render.png" /></p>
<p>Aqui estamos verificando a o valor de status, se for 'OK' chamamos a função <code>renderSuggestions</code>. <code>S.PlacesList</code> é um componente que eu criei usando styled components, é uma <code>ul</code> com algumas estilizações.</p>
<p>Agora vamos para a função <code>renderSuggestions</code> que é definida assim: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615318815289/4NXsYg8nC.png" alt="rederSU.png" /></p>
<p>Nessa função, fazemos uma <code>map</code> na <code>data</code> que é o que a API do google envia para gente e fazemos um 'destructuring', pegando as propriedades <code>places_id</code>(para ser usada como key do componente) e <code>main_text</code> e <code>secondary_text</code> que é realmente o texto dos lugares sugeridos.</p>
<p>Com essas informações em mãos, retornamos um <code>S.PlaceItem</code> (que é uma <code>li</code> criada com styled components) com uma key (o nosso <code>place_id</code>) e o texto primário e secundário das nossas sugestões. E como você percebeu, adicionamos uma função <code>handleSelect</code> na propriedade <code>onClick</code> que é usada para resgatar o valor do lugar que clicamos.</p>
<h2 id="selecionando-um-lugar-das-nossas-sugestoes">Selecionando um lugar das nossas sugestões</h2>
<p>Agora so falta  função de <code>handleSelect</code> que é uma implementação bem simples como vocês podem ver abaixo: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615319301584/RHC_vvhBV.png" alt="handle-seelect.png" /></p>
<p>onde apenas pegamos o valor que é mandado e o atribuimos ao value, logo depois usamos a função <code>clearSuggestions</code> que resgatamos do nosso hook. Pronto, tudo funcionando já!</p>
<h2 id="resgatando-latitude-e-longitude">Resgatando latitude e longitude</h2>
<p>Somente o nome do lugar pode não ser muito útil, mas podemos tambem resgatar a latitude e longitude do lugar que selecionamos. Para isso, adicione <code>getGeocode</code> e <code>getLatLng</code> as importações do <code>use-places-autocomplete</code> (entre chaves). Com essas funções podemos resgatar a latitude e longitude adicionando esse seguinte trecho de código a nossa função <code>handleSelect</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615319590341/89yPvM74u.png" alt="getLatLang.png" /></p>
<p>onde usamos o valor que recebemos na <code>handleSelect</code> ao chamar a função <code>getGeoCode</code> e o resultado dessa função usamos como parâmetro para chamar <code>getLatLng</code> e quando fazemos o destructuring, conseguimos o valor de lat de lng. Pronto, já temos uma informação a mais.</p>
<h1 id="finalizacao">Finalização</h1>
<p>Espero que esse pequeno tutorial tenha ajudado alguém, sendo na parte de acesso a API do google ou na utilização do pacote. Para mais informações você pode acessar a <a target="_blank" href="https://www.npmjs.com/package/use-places-autocomplete">documentação do pacote</a>.</p>
]]></content:encoded></item></channel></rss>