React JS - Construindo uma wiki de personagens Ricky e Morty - parte 1
Tags: react, beginners, tutorial, webdev
Published: 2022-07-27 12:32:06


Nesse primeiro tutorial de React, vamos fazer um "hands on lab" ou melhor "mão na massa" porque primeiramente será todo em português e segundo que será o mais prático possível sem entrar muito afundo sobre cada funcionalidade aqui utilizada. No final deste artigo você terá aprendido (ou não) alguns conceitos:

- Criar componentes;
- Usar filtros;
- Usar paginação;
- Usar react hooks (useState, useEffect);
- Criar rotas dinâmicas;
- Criar barra de pesquisa, navegação dentre outros;

Obs: Embora o artigo seja em português, todo o código será escrito em inglês por questões de boas práticas.

Então vamos lá!
Crie uma pasta com o nome 'react-wiki';
Abra essa pasta no VSCODE;

Agora abra o terminal (CTRL + ') e rode os seguintes comandos: 

1- O NPX é um package runner do NPM. Ele executa as bibliotecas que podem ser baixadas do site npmjs.
`npx create-react-app . ` 

2- Bootstrap é um framework front-end que fornece estruturas de CSS para a criação de sites e aplicações responsivas de forma rápida e simples.
`npm install bootstrap`

3- O Popper. js é uma biblioteca JavaScript que serve para posicionar elementos como menus, tooltips e popovers.
 `npm install @popperjs/core --save `

4- O SASS é uma linguagem de extensão do CSS, a sigla significa “Syntactically Awesome Style Sheets” traduzindo ao pé da letra, folhas de estilo com uma sintaxe incrível. A sua ideia é adicionar recursos especiais como variáveis, funções, operações e outras coisas.
`npm install sass`

5- O React Router é uma biblioteca do React que permite a navegação entre diversas partes da aplicação, como páginas.
`npm install react-router-dom`

6- O React Paginate é um componente que fará toda a paginação. Neste artigo vou mostrar apenas como implementá-lo sem entrar na lógica de funcionamento do mesmo. 
`npm install react-paginate --save`

E por último rode a aplicação para ver se está tudo OK com:
`npm start`


Tudo funcionando? Se sim, vc deve ter visto um logo do ReactJs girando na tela e ela provavelmente abriu no endereço "http://localhost:3000".

A aplicação que vamos desenvolver será uma "wiki" de personagens do desenho animado **Rick and Morty** e para isso vamos consumir a api pública que está neste endereço [https://rickandmortyapi.com](https://rickandmortyapi.com).
Toda documentação e como usar a api pode ser conferida na seção **_Docs_** ou na url [https://rickandmortyapi.com/documentation](https://rickandmortyapi.com/documentation).

Nossa aplicação terá um menu de navegação, barra de pesquisa, filtros por status, gênero (gender), espécies (species), episódios (episodes) e localização (location).
Segue uma figura pra ver a cara da aplicação final. No final do artigo vou deixar um link do app rodando para que possam conferir com mais detalhes.

![App final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ynhbfw00kjvyrlzaeame.png)

 ______________________________________________________

![App final 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g53k8e1r64sn6rn5x5va.png)
 
 ______________________________________________________


Nosso primeiro passo agora é criar uma estrutura de pasta para organizar o **app**.

Crie uma estrutura de pastas como essa abaixo: 
![fig1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wgqx6dlux5glrnmkr8br.PNG)

src > components > 
- Card
- Filter
- Navbar
- Pagination
- Search
 

Vamos remover todo o conteúdo do arquivo **App.css** e transformá-lo em arquivo SASS, basta renomeá-lo para **App.scss**;
Dentro desse arquivo vamos ter apenas uma classe css, mas já vamos nos habituar a usar como sass:

` .active {
  color: #0b5ed7 !important;
  font-weight: bold;
  border-bottom: 3px solid #0b5ed7;
}
`

Dentro do arquivo **App.js** vamos importar inicialmente o **_css_** do **bootstrap**, o **_js_** do **bootstrap** e os **_hooks_**  **useState** e **useEffect** do **react**. Suas primeiras linhas de código no arquivo **App.js**:

`import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap";
import React, { useState, useEffect } from "react";`

Antes de continuar vamos criar um card para exibir o resultado da api. 

##&nbsp;
##&nbsp;
##&raquo; Card
##&nbsp;
Agora criaremos um card para exibir o resultado da api que iremos utilizar. O resultado ficará igual a imagem abaixo:

![fig2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lmzbwpdjgfwjnpw9pkb.PNG)

 ______________________________________________________


Primeiro vamos criar um arquivo chamado **Card.module.scss** dentro da pasta **_Card_**. Ele terá o *css* do card e será interpretado pelo sass. 

Coloque o código abaixo:
```
$radius: 10px;

.card {
  border: 2px solid #0b5ed7;
  border-radius: $radius;
}

.content {
  padding: 10px;
}

.img {
  border-radius: $radius $radius 0 0;
}

.badge {
  top: 10px;
  right: 20px;
  font-size: 17px;
}
```
Pra quem nunca viu ou usou o sass. O uso do sass pode ser conferido na variável $radius. Ao atribuir um valor para $radius, podemos atribuí-la diretamente como valor para cada propriedade no   restante do css e ao mudar o valor da variável, mudará para todas as propriedades de uma vez, assim como fazemos com javascript por exemplo.


Dentro da pasta **_Card_**, crie um arquivo javascript **Card.js** e adicione o seguinte código: 
```
import React from "react";
import styles from "./Card.module.scss";

const Card = ({ page, results }) => {    

    let display;
  
    if (results) {
        display = results.map((x) => {
        let { id, image, name, status, location } = x;
      
          return (
            <div
            key={id}
            className="col-lg-4 col-md-6 col-sm-6 col-12 mb-4 position-relative text-dark"
          >
            <div className={`${styles.card} d-flex flex-column justify-content-center`}>
                <img className={`${styles.img} img-fluid`} src={image} alt="" />
                <div className={`${styles.content}`}>
                    <div className="fs-5 fw-bold mb-4">{name}</div>
                    <div className="">
                        <div className="fs-6 fw-normal">Last Location</div>
                        <div className="fs-5">{location.name}</div>
                    </div>
                </div>
            </div>
            </div>
        );
      });
      }
    else{
      display = "Nenhum personagem encontrado :/";
    }
  
    return <>{display}</>;
  }

  export default Card;
```
A função Card epera dois parametros "page" e "results". Ela mapeia o results e extrai as propriedades que vamos utilizar como id, image, name, etc. No "return" colocamos o html que queremos renderizar. Note algumas particularidades aqui, como ao invés de usar "class" no ReactJS usamos "className" para atribuir uma classe de css.
E para usar uma classe do arquivo scss, fazemos o que chamamos de interpolação e atribuimos com o "styles" na frente da classe, ou seja se quiser usar a classe "card" numa div tem que colocar algo similar a isso: 
```<div className={`${styles.card}`} >conteudo da div</div>```. 

Agora preparar o nosso **App.js** e adicionar o código abaixo para importar o card:
`import Card from "./components/Card/Card";`

e dentro da "function App()" vamos fazer uso dos **_hooks_** **useState** e **useEffect**, adicione o seguinte código:
```
  let [fetchedData, updateFetchedData] = useState([]);
  let { info, results } = fetchedData;
  
  let api = `https://rickandmortyapi.com/api/character/?page=1`;
  useEffect(() => {
    (async function () {
      let data = await fetch(api).then((res) => res.json());
      updateFetchedData(data);
    })();
  }, [api]);

  return (
    <div className="App">
    <h1 className="text-center mb-3">Personagens</h1>
    <div className="container">
    <div className="row">
      Filtro aparecerá aqui
      <div className="col-lg-8 col-12">
       
            <div className="row">
              <Card page="/" results={results} />
            </div>
         
      </div>
    </div>
    </div>
  </div>
  );
```

Aqui foi feito uma chamada para api e agora já vamos ver o resultado preenchendo nossos cards.


O Arquivo **App.js** completo deverá estar assim:
```
import 'bootstrap/dist/css/bootstrap.min.css';
import "bootstrap/dist/js/bootstrap";
import React, { useState, useEffect } from "react";
import Card from "./components/Card/Card";

function App() {
  let [fetchedData, updateFetchedData] = useState([]);
  let { info, results } = fetchedData;
  
  let api = `https://rickandmortyapi.com/api/character/?page=1`;
  useEffect(() => {
    (async function () {
      let data = await fetch(api).then((res) => res.json());
      updateFetchedData(data);
    })();
  }, [api]);

  return (
    <div className="App">
    <h1 className="text-center mb-3">Personagens</h1>
    <div className="container">
    <div className="row">
      Filtro aparecerá aqui
      <div className="col-lg-8 col-12">       
            <div className="row">
              <Card page="/" results={results} />
            </div>         
      </div>
    </div>
    </div>
  </div>
  );
}

export default App;

```
O resultado deverá ser igual a esse:

![fig3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cerhxi5n17caadlcdeb7.PNG)

Agora vamos acrescentar o indicador de status do personagem em nosso card, para isso coloque o seguinte código no retorno da função Card no arquivo **Card.js**: 

```
 {(() => {
            if (status === "Dead") {
              return (
                <div
                  className={`${styles.badge} position-absolute badge bg-danger`}
                >
                  {status}
                </div>
              );
            } else if (status === "Alive") {
              return (
                <div
                  className={`${styles.badge} position-absolute badge bg-success`}
                >
                  {status}
                </div>
              );
            } else {
              return (
                <div
                  className={`${styles.badge} position-absolute badge bg-secondary`}
                >
                  {status}
                </div>
              );
            }
          })()}
```
O código acima verifica o status e adiciona um "badge" da cor do status para cada card.
O Arquivo **Card.js** completo ficará assim: 

```
import React from "react";
import styles from "./Card.module.scss";


const Card = ({ page, results }) => {
    
    let display;
  
    if (results) {
        display = results.map((x) => {
        let { id, image, name, status, location } = x;
      
          return (
            <div
            key={id}
            className="col-lg-4 col-md-6 col-sm-6 col-12 mb-4 position-relative text-dark"
          >
            <div className={`${styles.card} d-flex flex-column justify-content-center`}>
                <img className={`${styles.img} img-fluid`} src={image} alt="" />
                <div className={`${styles.content}`}>
                    <div className="fs-5 fw-bold mb-4">{name}</div>
                    <div className="">
                        <div className="fs-6 fw-normal">Last Location</div>
                        <div className="fs-5">{location.name}</div>
                    </div>
                </div>
            </div>

            {(() => {
            if (status === "Dead") {
              return (
                <div
                  className={`${styles.badge} position-absolute badge bg-danger`}
                >
                  {status}
                </div>
              );
            } else if (status === "Alive") {
              return (
                <div
                  className={`${styles.badge} position-absolute badge bg-success`}
                >
                  {status}
                </div>
              );
            } else {
              return (
                <div
                  className={`${styles.badge} position-absolute badge bg-secondary`}
                >
                  {status}
                </div>
              );
            }
          })()}

            </div>
        );
      });
      }
    else{
      display = "Nenhum personagem encontrado :/";
    }
  
    return <>{display}</>;
  }

  export default Card;
```

O resultado dessa alteração será esse:

![fig4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2i5cft6ks1tbgytr4ekn.PNG)

##&nbsp;
##&nbsp;
##&raquo; Search
##&nbsp; 
O próximo passo será a criação do componente **Search** para pesquisar o card pelo termo digitado conforme a figura abaixo: 

![fig5.5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zidz5y83m3kcj35n8yi.png)

Primeiramente vamos criar 2 **_hooks_** do tipo **useState** para nos auxiliar na pesquisa. 

Dentro de **App.js** crie os **_hooks_** conforme o código abaixo:
`let [pageNumber, updatePageNumber] = useState(1);`
`let [search, setSearch] = useState("");`


Vamos precisar modificar a **url** da api (no arquivo **App.js**) para receber os parâmetros que iremos informar de agora em diante.

troque isso:
```
 let api = `https://rickandmortyapi.com/api/character/?page=1`;
```

por isso:
```
let api = `https://rickandmortyapi.com/api/character/?page=${pageNumber}&name=${search}`;
```

Dentro da pasta **_Sidebar_**, crie dois arquivos, um **Sidebar.js** e um arquivo **Search.module.scss** e coloque o código abaixo neste ultimo: 
````
.input {
  width: 40%; border-radius: 8px;
  border: 2px solid #0b5ed7;
  box-shadow: 1px 3px 9px rgba($color: #000000, $alpha: 0.25);
  padding: 10px 15px;
  &:focus { outline: none; }
}
.btn {
  box-shadow: 1px 3px 9px rgba($color: #000000, $alpha: 0.25);
}
@media (max-width: 576px) {
  .input { width: 80%; }
}
```
Esse css estilizará nossa barra e botão de pesquisa.

Agora vamos colocar o código do **Search.js** conforme abaixo:
```
import React from "react";
import styles from "./Search.module.scss";

const Search = ({ setSearch, updatePageNumber }) => {
  let searchBtn = (e) => {
    e.preventDefault();
  };
  return (
    <form
      className={`${styles.search} d-flex flex-sm-row flex-column align-items-center justify-content-center gap-4 mb-5`}
    >
      <input
        onChange={(e) => {
          updatePageNumber(1);
          setSearch(e.target.value);
        }}
        placeholder="Pesquisar por personagens..."
        className={styles.input}
        type="text"
      />
      <button
        onClick={searchBtn}
        className={`${styles.btn} btn btn-primary fs-5`}
      >
        Search
      </button>
    </form>
  );
};

export default Search;

```
O código acima faz com que o clique do botão como o digitar do campo texto, efetuem uma pesquisa.

Agora vamos importar o componente Search dentro do nosso **App.js**. Insira a linha de código logo abaixo da importação do Card:
` import Search from "./components/Search/Search"; `

Ainda no **App.js** colocamos o trecho de código a seguir logo abaixo o **H1** com o titulo da página: 
` <Search setSearch={setSearch} updatePageNumber={updatePageNumber} />`


Alterações feitas! Agora podemos testar, apenas digitando na barra de pesquisa já veremos o resultado acontecer.

![fig5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y94ftpk2zpzblwblwxuh.PNG)

__________________________________________________________________

![fig6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36zwyzw4uws3vkaeyqs8.PNG)
__________________________________________________________________

##&nbsp;
##&nbsp;
##&raquo; Pagination
##&nbsp;
 
Paginação! Chegou a hora de paginar a _p@rr#!_ toda! 
E pra fazer isso vamos usar um componente chamado **react-paginate**. 
Toda documentação e mais informações podem ser encontrada neste link [https://www.npmjs.com/package/react-paginate](https://www.npmjs.com/package/react-paginate). O intuito deste artigo não é aprofundar neste componente e sim mostrar sua implementação na prática.   

Então vamos lá!

Dentro da pasta **__Pagination__**, crie um arquivo **Pagination.js**. Nele vamos colocar o seguinte código para fazer nossa paginação:

```
import React, { useState, useEffect } from "react";
import ReactPaginate from "react-paginate";

const Pagination = ({ pageNumber, info, updatePageNumber }) => {
  let pageChange = (data) => {
    updatePageNumber(data.selected + 1);
  };

  const [width, setWidth] = useState(window.innerWidth);
  const updateDimensions = () => {
    setWidth(window.innerWidth);
  };
  useEffect(() => {
    window.addEventListener("resize", updateDimensions);
    return () => window.removeEventListener("resize", updateDimensions);
  }, []);

  return (
    <>
      <style jsx>
        {`
          @media (max-width: 768px) {
            .pagination {
              font-size: 12px;
            }
            .next,
            .prev {
              display: none;
            }
          }
          @media (max-width: 768px) {
            .pagination {
              font-size: 14px;
            }
          }
        `}
      </style>
      <ReactPaginate
        className="pagination justify-content-center my-4 gap-4"
        nextLabel="Next"
        forcePage={pageNumber === 1 ? 0 : pageNumber - 1}
        previousLabel="Prev"
        previousClassName="btn btn-primary fs-5 prev"
        nextClassName="btn btn-primary fs-5 next"
        activeClassName="active"
        marginPagesDisplayed={width < 576 ? 1 : 2}
        pageRangeDisplayed={width < 576 ? 1 : 2}
        pageCount={info?.pages}
        onPageChange={pageChange}
        pageClassName="page-item"
        pageLinkClassName="page-link"
      />
    </>
  );
};

export default Pagination;

```
No código acima temos uma função que recebe os parâmetros "pageNumber", "info" e "updatePageNumber" e atualiza os dados de acordo com a pagina informada. O ReactPaginate tem algumas propriedades que podem ser conferidas na documentação no site do componente.

No **App.js** colocamos bem próximo ao fechamento da ultima div o seguinte trecho de código:

`<Pagination info={info} pageNumber={pageNumber} updatePageNumber={updatePageNumber}/>`

A essa altura o seu arquivo **App.js** deve estar assim:

```
import 'bootstrap/dist/css/bootstrap.min.css';
import "bootstrap/dist/js/bootstrap";
import React, { useState, useEffect } from "react";
import Card from "./components/Card/Card";
import Search from "./components/Search/Search"
import Pagination from "./components/Pagination/Pagination"

function App() {
  
  let [fetchedData, updateFetchedData] = useState([]);
  let { info, results } = fetchedData;
  let [pageNumber, updatePageNumber] = useState(1);
  let [search, setSearch] = useState("");
  
  let api = `https://rickandmortyapi.com/api/character/?page=${pageNumber}&name=${search}`;
  useEffect(() => {
    (async function () {
      let data = await fetch(api).then((res) => res.json());
      updateFetchedData(data);
    })();
  }, [api]);

  return (
    <div className="App">
    <h1 className="text-center mb-3">Personagens</h1>
    
    <Search setSearch={setSearch} updatePageNumber={updatePageNumber} />
      
    <div className="container">
    <div className="row">
      Filtro aparecerá aqui
      <div className="col-lg-8 col-12">       
            <div className="row">
              <Card page="/" results={results} />
            </div>         
      </div>
    </div>
    </div>

    <Pagination
        info={info}
        pageNumber={pageNumber}
        updatePageNumber={updatePageNumber}
      />
  </div>
  );
}

export default App;


```

E o resultado tem que estar parecido com a figura abaixo:


![fig7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u22s0sh6qb1bjbgfb9n6.gif)

 





##&nbsp;






Continua... 

[Parte 2](https://dev.to/dooramos/react-js-construindo-uma-wiki-de-personagens-ricky-e-morty-parte-2-51dm)