<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Gustavo Cavalieri - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Aprenda a codificar - de graça. Tutoriais de programação em Python, JavaScript, Linux e muito mais. ]]>
        </description>
        <link>https://www.freecodecamp.org/portuguese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Gustavo Cavalieri - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/portuguese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 25 May 2026 19:55:27 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/portuguese/news/author/gugacavalieri/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Como criar seu próprio hook do React: um guia passo a passo ]]>
                </title>
                <description>
                    <![CDATA[ Os hooks do React são uma customização essencial que permite que você adicione funcionalidades exclusivas às suas aplicações React. Em vários casos, se você quer adicionar uma certa funcionalidade à sua aplicação, pode simplesmente adicionar uma biblioteca de terceiros que já existe para resolver o seu problema. Se tal biblioteca ]]>
                </description>
                <link>https://www.freecodecamp.org/portuguese/news/como-criar-seu-proprio-hook-do-react-um-guia-passo-a-passo/</link>
                <guid isPermaLink="false">66ca838afc5d380414eff9b0</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gustavo Cavalieri ]]>
                </dc:creator>
                <pubDate>Mon, 23 Sep 2024 21:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/how-to-create-custom-react-hooks.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artigo original:</strong> <a href="https://www.freecodecamp.org/news/how-to-create-react-hooks/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build Your Own React Hooks: A Step-by-Step Guide</a>
      </p><p>Os <em>hooks</em> do React são uma customização essencial que permite que você adicione funcionalidades exclusivas às suas aplicações React.</p><p>Em vários casos, se você quer adicionar uma certa funcionalidade à sua aplicação, pode simplesmente adicionar uma biblioteca de terceiros que já existe para resolver o seu problema. Se tal biblioteca não existir, porém, o que fazer?</p><p>Como um desenvolvedor de React, é importante aprender os processos de criar <em>hooks</em> customizáveis para resolver problemas ou adicionar funcionalidades que faltam aos seus projetos.</p><p>Neste guia passo a passo, eu vou mostrar como criar os seus próprios <em>hooks</em> do React decompondo três <em>hooks</em> que fiz para as minhas aplicações e mostrando quais problemas eles foram criados para resolver.</p><h2 id="1-usecopytoclipboard">1. useCopyToClipboard</h2><p>Em uma versão anterior do meu site, <a href="https://www.freecodecamp.org/portuguese/news/p/641c40f4-2030-4bf3-bb94-c0983637c274/reedbarger.com">reedbarger.com</a>, eu permitia que usuários copiassem o código dos meus artigos com a ajuda de um pacote chamado <code>react-copy-to-clipboard</code>.</p><p>Quando um usuário passa o mouse sobre o trecho de código, clica no botão de copiar e o código é adicionado para a área de transferência, ele pode, então, colar e usar o código onde quiser.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/fnmmit9fvxb4lejz3dcm.gif" class="kg-image" alt="fnmmit9fvxb4lejz3dcm" width="1280" height="720" loading="lazy"></figure><p>Ao invés de usar uma biblioteca de terceiros, no entanto, eu queria recriar essa funcionalidade com o meu próprio <em>hook </em>do React. Como qualquer outro <em>hook</em> customizável que crio, eu os coloco em uma pasta dedicada, geralmente chamada <code>utils</code> ou <code>lib</code>, especificamente, para funções que posso reutilizar na minha aplicação.</p><p>Colocaremos esse <em>hook</em> em um arquivo chamado <code>useCopyToClipboard.js</code> e criaremos uma função com o mesmo nome.</p><p>Existem várias maneiras de se copiar um texto para a área de transferência de um usuário. Prefiro utilizar uma biblioteca para isso, chamada <code>copy-to-clipboard</code>, que torna o processo mais confiável.</p><p>Ela exporta uma função que chamaremos de <code>copy</code>.</p><pre><code class="language-jsx">// utils/useCopyToClipboard.js
import React from "react";
import copy from "copy-to-clipboard";

export default function useCopyToClipboard() {}
</code></pre><p>Em seguida, criaremos uma função que será utilizada para copiar qualquer texto que deve ser adicionado a área de transferência de um usuário. Chamaremos essa função de <code>handleCopy</code>.</p><h3 id="como-criar-a-fun-o-handlecopy"><strong>Como criar a função <code>handleCopy</code></strong></h3><p>Nessa função, primeiramente, precisamos nos assegurar de que ela aceitará apenas parâmetros do tipo String ou Number. Colocaremos uma declaração com <code>if-else</code>, que garantirá que o tipo é uma String ou um Number. Caso contrário, imprimiremos um erro no console, que avisará o usuário de que ele não pode copiar qualquer outro tipo.</p><pre><code class="language-jsx">import React from "react";
import copy from "copy-to-clipboard";

export default function useCopyToClipboard() {
  const [isCopied, setCopied] = React.useState(false);

  function handleCopy(text) {
    if (typeof text === "string" || typeof text == "number") {
      // pode copiar
    } else {
      // não pode copiar
      console.error(
        `Não é permitido copiar o tipo ${typeof text} para a área de transferência; deve ser uma String ou um Number.`
      );
    }
  }
}</code></pre><p>Em seguida, devemos converter o parâmetro <code>text</code> para String e passá-lo para a função <code>copy</code>. Depois, retornamos a função <code>handleCopy</code> dentro do <em>hook </em>para ser utilizada em qualquer lugar da nossa aplicação.</p><p>Normalmente, a função <code>handleCopy</code> será conectada a um evento <code>onClick</code> de um botão.</p><pre><code class="language-jsx">import React from "react";
import copy from "copy-to-clipboard";

export default function useCopyToClipboard() {
  function handleCopy(text) {
    if (typeof text === "string" || typeof text == "number") {
      copy(text.toString());
    } else {
      console.error(
        `Não é permitido copiar o tipo ${typeof text} para a área de transferência, deve ser uma String ou um Number.`
      );
    }
  }

  return handleCopy;
}</code></pre><p>Além disso, queremos algum <em>state</em> que represente se o texto foi copiado ou não. Para criar esse comportamento, chamaremos <code>useState</code> no início do nosso <em>hook</em> e criaremos uma variável de estado <code>isCopied</code>, onde o método de atribuição se chamará <code>setCopy</code>.</p><p>Inicialmente, esse valor será <code>false</code>. Se o texto é copiado com sucesso, alteraremos a variável <code>copy</code> para <code>true</code>. Caso contrário, a alteraremos para <code>false</code>.</p><p>Finalmente, fazemos o <em>hook </em>retornar <code>isCopied</code> e <code>handleCopy</code> utilizando um array.</p><pre><code class="language-jsx">import React from "react";
import copy from "copy-to-clipboard";

export default function useCopyToClipboard(resetInterval = null) {
  const [isCopied, setCopied] = React.useState(false);

  function handleCopy(text) {
    if (typeof text === "string" || typeof text == "number") {
      copy(text.toString());
      setCopied(true);
    } else {
      setCopied(false);
      console.error(
        `Não é permitido copiar o tipo ${typeof text} para a área de transferência, deve ser uma String ou um Number.`
      );
    }
  }

  return [isCopied, handleCopy];
}
</code></pre><h3 id="como-utilizar-o-hook-usecopytoclipboard">Como utilizar o hook useCopyToClipboard</h3><p>Agora, podemos utilizar <code>useCopyToClipboard</code> dentro de qualquer componente que desejarmos.</p><p>Nesse caso, vou utilizá-lo com um componente de botão de copiar, que receberá o trecho de código que deve ser copiado.</p><p>Para fazer isso funcionar, tudo que precisamos é adicionar um evento de <code>onClick</code> ao botão. Nos parâmetros do nosso componente, recebemos código que será copiado para a área de transferência. Quando o código é copiado, podemos mostrar um ícone diferente indicando que o código foi copiado com sucesso.</p><pre><code class="language-jsx">import React from "react";
import ClipboardIcon from "../svg/ClipboardIcon";
import SuccessIcon from "../svg/SuccessIcon";
import useCopyToClipboard from "../utils/useCopyToClipboard";

function CopyButton({ code }) {
  const [isCopied, handleCopy] = useCopyToClipboard();

  return (
    &lt;button onClick={() =&gt; handleCopy(code)}&gt;
      {isCopied ? &lt;SuccessIcon /&gt; : &lt;ClipboardIcon /&gt;}
    &lt;/button&gt;
  );
}
</code></pre><h3 id="como-adicionar-um-intervalo-de-reinicializa-o">Como adicionar um intervalo de reinicialização</h3><p>Existe uma melhoria que podemos adicionar no nosso código. Na versão atual, <code>isCopied</code> sempre será <code>true</code>, e sempre veremos o ícone de sucesso:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/pgpdz9f5xp7nr4twovsn.gif" class="kg-image" alt="pgpdz9f5xp7nr4twovsn" width="1280" height="720" loading="lazy"></figure><p>Se quisermos reinicializar o <em>state</em> do componente após alguns segundos, podemos passar um intervalo de tempo como parâmetro para <code>useCopyToClipboard</code>. Adicionaremos essa funcionalidade.</p><p>De volta ao nosso <em>hook</em>, podemos criar um parâmetro chamado <code>resetInterval</code>, que terá como valor padrão <code>null</code>, o que garantirá que o <em>state</em> não será reinicializado se nenhum argumento for passado para ele.</p><p>Adicionaremos também a função <code>useEffect</code> para implementar o seguinte comportamento: se o texto for copiado e tivermos um intervalo de reinicialização, alteraremos o valor de <code>isCopied</code> novamente para <code>false</code> após o intervalo utilizando a função <code>setTimeout</code>.</p><p>Além disso, precisamos remover esse comportamento se o nosso o componente que está utilizando o nosso <em>hook</em> de desmontar (<em>unmount</em>). Ou seja, no caso de não existir mais <em>state</em> ou componente para ser atualizado.</p><pre><code class="language-jsx">import React from "react";
import copy from "copy-to-clipboard";

export default function useCopyToClipboard(resetInterval = null) {
  const [isCopied, setCopied] = React.useState(false);

  const handleCopy = React.useCallback((text) =&gt; {
    if (typeof text === "string" || typeof text == "number") {
      copy(text.toString());
      setCopied(true);
    } else {
      setCopied(false);
      console.error(
        `Não é permitido copiar o tipo ${typeof text} para a área de transferência, deve ser uma String ou um Number.`
      );
    }
  }, []);

  React.useEffect(() =&gt; {
    let timeout;
    if (isCopied &amp;&amp; resetInterval) {
      timeout = setTimeout(() =&gt; setCopied(false), resetInterval);
    }
    return () =&gt; {
      clearTimeout(timeout);
    };
  }, [isCopied, resetInterval]);

  return [isCopied, handleCopy];
}
</code></pre><p>Finalmente, a última melhoria que podemos fazer é envolver <code>handleCopy</code> em um <em>hook</em> <code>useCallback</code> para garantir que a função não seja recriada todas as vezes em que a tela for renderizada novamente.</p><h3 id="resultado">Resultado</h3><p>Com isso, temos o nosso <em>hook </em>que permite que o <em>state</em> seja reinicializado após um intervalo de tempo. Se passarmos um valor de tempo, veremos o resultado abaixo.</p><pre><code class="language-jsx">import React from "react";
import ClipboardIcon from "../svg/ClipboardIcon";
import SuccessIcon from "../svg/SuccessIcon";
import useCopyToClipboard from "../utils/useCopyToClipboard";

function CopyButton({ code }) {
  // isCopied é reinicializada após 3 segundos
  const [isCopied, handleCopy] = useCopyToClipboard(3000);

  return (
    &lt;button onClick={() =&gt; handleCopy(code)}&gt;
      {isCopied ? &lt;SuccessIcon /&gt; : &lt;ClipboardIcon /&gt;}
    &lt;/button&gt;
  );
}
</code></pre><h2 id="2-hook-usepagebottom">2. hook usePageBottom </h2><p>Em aplicações do React, muitas vezes, é importante saber quando o usuário rolou até o final da página.</p><p>Em aplicações que possuem "rolagem infinita", como o Instagram, por exemplo, quando o usuário chega no fim da página, a aplicação busca mais postagens.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/4dav187wpkl46skhhjgh.gif" class="kg-image" alt="4dav187wpkl46skhhjgh" width="1280" height="720" loading="lazy"></figure><p>Veremos como criar um <em>hook</em> <code>usePageBottom</code> para casos em que precisamos implementar uma página de rolagem infinita.</p><p>Começaremos criando um arquivo, <code>usePageBottom.js</code> na nossa pasta <code>utils</code> e adicionaremos uma função (<em>hook</em>) com o mesmo nome:</p><pre><code class="language-js">// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {}

</code></pre><p>Em seguida, precisamos calcular quando o usuário chega no final da página. Podemos determinar essa informação através do objeto <code>window</code>. Para acessar esse objeto, precisamos nos certificar de que o <em>hook </em>é chamado de um componente já montado (<em>isMounted</em>). Então, utilizaremos a função <code>useEffect</code> com um <em>array </em>vazio.</p><pre><code class="language-js">// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  React.useEffect(() =&gt; {}, []);
}

</code></pre><p>O usuário terá atingido o final da página quando o valor de <code>window.innerHeight</code> somado com <code>document.scrollTop</code> é igual ao valor de <code>document.offsetHeight</code>. Se esses dois valores são iguais, o resultado será <code>true</code> e o usuário terá atingido o final da página.</p><pre><code class="language-js">// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  React.useEffect(() =&gt; {
    window.innerHeight + document.documentElement.scrollTop === 
    document.documentElement.offsetHeight;
  }, []);
}

</code></pre><p>Salvaremos o resultado dessa expressão em uma variável <code>isBottom</code>, e atualizaremos uma variável de estado chamada <code>bottom</code>, que depois será retornada pelo nosso <em>hook</em>.</p><pre><code class="language-js">// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  const [bottom, setBottom] = React.useState(false);

  React.useEffect(() =&gt; {
    const isBottom =
      window.innerHeight + document.documentElement.scrollTop ===
      document.documentElement.offsetHeight;
    setBottom(isButton);
  }, []);

  return bottom;
}

</code></pre><p>Nosso código atual, no entanto, isso não funcionará. Por quê?</p><p>O problema está no fato que temos que calcular <code>isBottom</code> toda vez que o usuário está rolando a página. Por isso, precisamos receber eventos de rolagem da página utilizando <code>window.addEventListener</code>. Podemos criar uma função local que será chamada toda vez que o usuário rolar a página – a chamaremos de <code>handleScroll</code>.</p><pre><code class="language-js">// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  const [bottom, setBottom] = React.useState(false);

  React.useEffect(() =&gt; {
    function handleScroll() {
      const isBottom =
        window.innerHeight + document.documentElement.scrollTop 
        === document.documentElement.offsetHeight;
      setBottom(isButton);
    }
    window.addEventListener("scroll", handleScroll);
  }, []);

  return bottom;
}

</code></pre><p>Finalmente, como temos um "<em>event listener</em>" que atualizará o <em>state</em>, precisamos tratar o caso de o usuário sair da página e o nosso componente ser removido. Precisamos remover esse "<em>event listener</em>" que adicionamos para evitar que uma variável de estado que não existe mais seja alterada.</p><p>Podemos fazer isso retornando a função <code>window.removeEventListener</code>, onde passamos uma referência para a função <code>handleScroll</code>. Agora, tudo certo!</p><pre><code class="language-js">// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  const [bottom, setBottom] = React.useState(false);

  React.useEffect(() =&gt; {
    function handleScroll() {
      const isBottom =
        window.innerHeight + document.documentElement.scrollTop 
        === document.documentElement.offsetHeight;
      setBottom(isButton);
    }
    window.addEventListener("scroll", handleScroll);
    return () =&gt; {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return bottom;
}

</code></pre><p>Podemos simplesmente chamar esse código em qualquer função em que queremos saber se o final da página foi atingido ou não.</p><p>Em um dos meus sites do Gatsby, eu tenho um cabeçalho. Conforme diminuo o tamanho da página, eu quero mostrar menos links para o usuário.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/kxbnn3jmwjarkc8zrpbm.gif" class="kg-image" alt="kxbnn3jmwjarkc8zrpbm" width="1280" height="720" loading="lazy"></figure><p>Para fazermos isso, podemos utilizar uma Media Query (CSS) ou podemos utilizar um <em>hook</em> do React para nos retornar o tamanho da página e exibir ou esconder os links no código JSX.</p><p>Anteriormente, eu estava utilizando um <em>hook</em> de uma biblioteca chamada <code>react-use</code>. Ao invés de utilizar uma biblioteca de terceiros, eu decidi criar meu próprio <em>hook</em> para retornar as dimensões da página, tanto largura quanto altura. Chamei esse <em>hook</em> de <code>useWindowSize</code>.</p><h3 id="como-criar-o-hook">Como criar o <em>hook</em></h3><p>Primeiramente, criaremos um arquivo <code>.js</code> na nossa pasta <code>utils</code>. Vamos chamá-lo de <code>useWindowSize</code>. Importaremos o React (para utilizar <em>hooks</em>) e exportaremos o <em>hook</em> atual.<br></p><pre><code class="language-js">// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {}

</code></pre><p>Como estou usando esse <em>hook </em>em um site do Gatsby.js, que é renderizado no servidor, eu preciso obter o tamanho da janela. Porém, talvez não tenhamos acesso ao tamanho da janela, pois estamos renderizando a página no servidor.</p><p>Para ter certeza de que não estamos no servidor, podemos verificar se o tipo de <code>window</code> não é igual a <code>undefined</code>.</p><p>Nesse caso, podemos retornar uma largura e altura para um navegador padrão. Retornaremos 1200 e 800 em um objeto.</p><pre><code class="language-js">// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  if (typeof window !== "undefined") {
    return { width: 1200, height: 800 };
  }
}

</code></pre><h3 id="como-obter-a-largura-e-altura-da-janela">Como obter a largura e altura da janela</h3><p>Quando estamos no <em>client</em> (navegador) e podemos utilizar a janela (<em>window</em>), temos a opção de usar o <em>hook</em> <code>useEffect</code> para interagir com a janela. Incluiremos um array vazio de dependências para ter certeza que o nosso <em>hook</em> é chamado apenas quando o componente pai está montado (<em>mounted</em>).</p><p>Para descobrir a largura e altura da janela, podemos adicionar um <em>event listener</em> para escutar por eventos do tipo <code>resize</code>. Toda vez que o tamanho da janela mudar, podemos atualizar uma parte do <em>state</em> (criado com <code>useState</code>), que chamaremos de <code>windowSize</code>, chamaremos o método de atualização de <code>setWindowSize</code>.<br></p><pre><code class="language-js">// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  if (typeof window !== "undefined") {
    return { width: 1200, height: 800 };
  }

  const [windowSize, setWindowSize] = React.useState();

  React.useEffect(() =&gt; {
    window.addEventListener("resize", () =&gt; {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    });
  }, []);
}

</code></pre><p>Quando a janela é redimensionada, o nosso <em>event listener</em> será chamado e o <em>state</em> de <code>windowSize</code> será atualizado com as novas dimensões. Para realizar isso, atualizamos a largura para <code>window.innerWidth</code> e a altura para <code>window.innerHeight</code>.</p><h3 id="como-adicionar-suporte-a-server-side-rendering">Como adicionar suporte a <em>Server Side Rendering</em></h3><p>No entanto, o nosso código atual não funcionará. Isso acontece porque existe uma regra dos <em>hooks</em> que os impedem de serem chamados condicionalmente. Como resultado, não podemos ter uma condicional nos métodos <code>useState</code> ou <code>useEffect</code> antes de eles serem chamados.</p><p>Então, para contornar esse problema, definiremos o valor inicial de <code>useState</code> condicionalmente. Criaremos uma variável chamada <code>isSSR</code>, que ficará responsável por verificar se a janela não é igual a uma string <code>undefined</code>.</p><p>Depois, utilizaremos um ternário para definir a largura e altura e checando primeiramente se estamos no servidor ou no navegador. Se estivermos no servidor, usaremos um valor padrão. Caso contrário, usaremos <code>window.innerWidth</code> e <code>window.innerHeight</code>.</p><pre><code class="language-js">// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  // if (typeof window !== "undefined") {
  // return { width: 1200, height: 800 };
  // }
  const isSSR = typeof window !== "undefined";
  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  React.useEffect(() =&gt; {
    window.addEventListener("resize", () =&gt; {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    });
  }, []);
}

</code></pre><p>Finalmente, precisamos pensar em como os nossos componentes desmontarão (<em>unmount</em>). O que precisamos fazer? É necessária a remoção do <em>listener</em> de redimensionamento.</p><h3 id="como-remover-o-event-listener-de-redimensionamento">Como remover o <em>event listener</em> de redimensionamento</h3><p>Podemos fazer isso retornando uma função no método <code>useEffect</code>. Removeremos o <em>listener</em> com <code>window.removeEventListener</code>.</p><pre><code class="language-js">// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  // if (typeof window !== "undefined") {
  // return { width: 1200, height: 800 };
  // }
  const isSSR = typeof window !== "undefined";
  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  React.useEffect(() =&gt; {
    window.addEventListener("resize", () =&gt; {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    });

    return () =&gt; {
      window.removeEventListener("resize", () =&gt; {
        setWindowSize({ width: window.innerWidth, height: window.innerHeight });
      });
    };
  }, []);
}

</code></pre><p>Precisamos, contudo, de uma referência para a mesma função e não de dois métodos diferentes como temos aqui. Para fazer isso, criaremos uma função de <em>callback</em> para os dois <em>listeners</em>, chamada <code>changeWindowSize</code>.</p><p>Para encerrar, no final do <em>hook</em>, retornaremos o <em>state</em> de <code>windowSize</code>.</p><pre><code class="language-js">// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  const isSSR = typeof window !== "undefined";
  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  function changeWindowSize() {
    setWindowSize({ width: window.innerWidth, height: window.innerHeight });
  }

  React.useEffect(() =&gt; {
    window.addEventListener("resize", changeWindowSize);

    return () =&gt; {
      window.removeEventListener("resize", changeWindowSize);
    };
  }, []);

  return windowSize;
}

</code></pre><h3 id="resultado-1">Resultado</h3><p>Para utilizar o <em>hook</em>, precisamos importá-lo onde ele é requerido, chamá-lo e utilizar a largura da janela retornada quando queremos esconder ou mostrar certos elementos na tela.</p><p>No exemplo abaixo, utilizaremos 500px como a nossa condição. Aqui, queremos esconder todos os links e mostrar apenas o botão "<em>Join Now</em>":</p><pre><code class="language-jsx">// components/StickyHeader.js

import React from "react";
import useWindowSize from "../utils/useWindowSize";

function StickyHeader() {
  const { width } = useWindowSize();

  return (
    &lt;div&gt;
      {/* visible only when window greater than 500px */}
      {width &gt; 500 &amp;&amp; (
        &lt;&gt;
          &lt;div onClick={onTestimonialsClick} role="button"&gt;
            &lt;span&gt;Testimonials&lt;/span&gt;
          &lt;/div&gt;
          &lt;div onClick={onPriceClick} role="button"&gt;
            &lt;span&gt;Price&lt;/span&gt;
          &lt;/div&gt;
          &lt;div&gt;
            &lt;span onClick={onQuestionClick} role="button"&gt;
              Question?
            &lt;/span&gt;
          &lt;/div&gt;
        &lt;/&gt;
      )}
      {/* visible at any window size */}
      &lt;div&gt;
        &lt;span className="primary-button" onClick={onPriceClick} role="button"&gt;
          Join Now
        &lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

</code></pre><p>Esse <em>hook </em>funcionará em qualquer aplicação do React renderizada no servidor, como as que usam Gatsby e Next.js.</p><h2 id="3-hook-usedevicedetect">3. hook useDeviceDetect</h2><p>Estou criando uma <em>landing page</em> para um curso e há um comportamento muito estranho quando a página é acessada por dispositivos móveis. Em computadores de mesa, está tudo certo.</p><p>Quando uso um dispositivo móvel, porém, tudo parece fora de lugar.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/n69a3h184fhniah3g1z8.gif" class="kg-image" alt="n69a3h184fhniah3g1z8" width="1280" height="720" loading="lazy"></figure><p>Descobri que o problema está relacionado com uma biblioteca chamada <code>react-device-detect</code>, que eu estava utilizando para detectar se os usuários acessavam o site de um dispositivo móvel ou não. Em caso afirmativo, o cabeçalho da página deve ser escondido.</p><pre><code class="language-jsx">// templates/course.js
import React from "react";
import { isMobile } from "react-device-detect";

function Course() {
  return (
    &lt;&gt;
      &lt;SEO /&gt;
      {!isMobile &amp;&amp; &lt;StickyHeader {...courseData} /&gt;}
      {/* more components... */}
    &lt;/&gt;
  );
}

</code></pre><p>O problema é que essa biblioteca não oferece suporte para renderização no servidor (em inglês, <em>server-side rendering</em>), que é o que o Gatsby usa por padrão. Então, precisei criar a minha própria solução para identificar quando o usuário acessa o site de um dispositivo móvel. Para isso, eu decidi criar um <em>hook</em> customizável chamado <code>useDeviceDetect</code>.</p><h3 id="como-criei-o-hook">Como criei o <em>hook</em></h3><p>Criei um arquivo separado para esse <em>hook </em>na pasta <code>utils</code>, chamado <code>useDeviceDetect.js</code>. Como <em>hooks </em>são simples funções do JavaScript que podem ser reaproveitadas, eu criei uma função chamada <code>useDeviceDetect</code> e importei as bibliotecas do React.</p><pre><code class="language-jsx">// utils/useDeviceDetect.js
import React from "react";

export default function useDeviceDetect() {}

</code></pre><h3 id="como-descobrir-o-user-agent-atrav-s-da-window">Como descobrir o <em>User Agent</em> através da <code>window</code></h3><p>Um jeito de descobrir informações sobre o dispositivo do usuário é através da propriedade <code>userAgent</code> (que está no objeto <code>navigator</code>, dentro de <code>window</code>).</p><p>Como interagir com a API <code>window</code> pode ser considerado um efeito colateral, precisamos acessar a propriedade "<em>user agent</em>" dentro do <em>hook </em><code>useEffect</code>.</p><pre><code class="language-jsx">// utils/useDeviceDetect.js
import React from "react";

export default function useDeviceDetect() {
  React.useEffect(() =&gt; {
    console.log(`Dispositivo do usuário: ${window.navigator.userAgent}`);
    // também pode ser escrito como 'navigator.userAgent'
  }, []);
}

</code></pre><p>Quando o componente for montado (<em>mounted</em>), podemos usar <code>typeof navigator</code> para determinar se estamos no navegador ou no servidor. Se estivermos no servidor, não teremos acesso ao <code>window</code>. Nesse caso, <code>typeof navigator</code> será igual à String <code>undefined</code>. Caso contrário, estamos no <em>client</em> e teremos acesso à propriedade "<em>user agent</em>".</p><p>Podemos expressar a lógica acima utilizando um ternário para obter a propriedade <code>userAgent</code>.</p><pre><code class="language-jsx">// utils/useDeviceDetect.js
import React from "react";

export default function useDeviceDetect() {
  React.useEffect(() =&gt; {
    const userAgent =
      typeof navigator === "undefined" ? "" : navigator.userAgent;
  }, []);
}

</code></pre><h3 id="como-checar-se-useragent-um-dispositivo-m-vel">Como checar se userAgent é um dispositivo móvel</h3><p><code>userAgent</code> é uma String que terá um dos valores abaixo caso o usuário esteja em um dispositivo móvel:</p><p>Android, BlackBerry, iPhone, iPad, iPod, Opera Mini, IEMobile, ou WPDesktop.</p><p>Tudo que precisamos fazer é utilizar a propriedade <code>userAgent</code> e o método <code>.match()</code> com uma expressão regular para checar se o valor é alguma das opções acima. Guardaremos o resultado em uma variável local chamada <code>mobile</code>.</p><p>Armazenaremos esse resultado no estado utilizando o <em>hook </em><code>useState</code>, que terá o valor inicial de <code>false</code>. Para isso, criamos a variável de estado <code>isMobile</code> e o método de atribuição <code>setMobile</code>.</p><pre><code class="language-jsx">// utils/useDeviceDetect.js
import React from "react";

export default function useDeviceDetect() {
  const [isMobile, setMobile] = React.useState(false);

  React.useEffect(() =&gt; {
    const userAgent =
      typeof window.navigator === "undefined" ? "" : navigator.userAgent;
    const mobile = Boolean(
      userAgent.match(
        /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
      )
    );
    setMobile(mobile);
  }, []);
}

</code></pre><p>Então, quando definirmos o valor de <code>mobile</code>, vamos guardá-lo no <em>state</em>. Finalmente, retornaremos um objeto no caso de precisarmos adicionar novas informações e funcionalidades à esse <em>hook</em>.</p><p>No objeto de retorno, adicionaremos <code>isMobile</code> como uma propriedade e seu valor.</p><pre><code class="language-jsx">// utils/useDeviceDetect.js
import React from "react";

export default function useDeviceDetect() {
  const [isMobile, setMobile] = React.useState(false);

  React.useEffect(() =&gt; {
    const userAgent =
      typeof window.navigator === "undefined" ? "" : navigator.userAgent;
    const mobile = Boolean(
      userAgent.match(
        /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
      )
    );
    setMobile(mobile);
  }, []);

  return { isMobile };
}

</code></pre><h3 id="resultado-2">Resultado</h3><p>De volta a nossa <em>landing page</em>, podemos executar o <em>hook</em> e utilizar o valor da propriedade <code>isMobile</code> onde quisermos.</p><pre><code class="language-jsx">// templates/course.js
import React from "react";
import useDeviceDetect from "../utils/useDeviceDetect";

function Course() {
  const { isMobile } = useDeviceDetect();

  return (
    &lt;&gt;
      &lt;SEO /&gt;
      {!isMobile &amp;&amp; &lt;StickyHeader {...courseData} /&gt;}
      {/* more components... */}
    &lt;/&gt;
  );
}

</code></pre><h2 id="conclus-o">Conclusão</h2><p>Como eu tentei demonstrar nos exemplos acima, os <em>hooks </em>do React podem nos fornecer as ferramentas necessárias para consertar os problemas quando bibliotecas externas falharem.</p><p>Espero que este guia tenha dado a você uma ideia melhor de como criar os seus próprios <em>hooks</em> em React. Fique à vontade para usar qualquer um dos <em>hooks </em>acima nos seus projetos ou como inspiração para criar outros <em>hooks</em>.</p><h2 id="torne-se-um-desenvolvedor-de-react-profissional">Torne-se um desenvolvedor de React profissional</h2><p>React é difícil. Você não precisa aprendê-lo sozinho.</p><p>Coloquei tudo o que sei sobre React em um único curso, para ajudar você a alcançar seus objetivos em tempo recorde:</p><p>Apresento a vocês: <a href="https://www.thereactbootcamp.com/"><strong>The React Bootcamp</strong></a> (em inglês)</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/portuguese/news/content/images/2024/09/react-bootcamp-cta-alt-1.png" class="kg-image" alt="react-bootcamp-cta-alt-1" srcset="https://www.freecodecamp.org/portuguese/news/content/images/size/w600/2024/09/react-bootcamp-cta-alt-1.png 600w, https://www.freecodecamp.org/portuguese/news/content/images/size/w1000/2024/09/react-bootcamp-cta-alt-1.png 1000w, https://www.freecodecamp.org/portuguese/news/content/images/2024/09/react-bootcamp-cta-alt-1.png 1105w" sizes="(min-width: 720px) 720px" width="1105" height="394" loading="lazy"></figure><p><strong>É o curso que eu gostaria de ter feito quando comecei a aprender React.</strong></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
