DEV Community

Cover image for React JS - Construindo uma wiki de personagens Ricky e Morty - parte 1
Eduardo Ramos
Eduardo Ramos

Posted on

React JS - Construindo uma wiki de personagens Ricky e Morty - parte 1

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.
Toda documentação e como usar a api pode ser conferida na seção Docs ou na url 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


App final 2


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

Crie uma estrutura de pastas como essa abaixo:
fig1

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.

 

 

» Card

 

Agora criaremos um card para exibir o resultado da api que iremos utilizar. O resultado ficará igual a imagem abaixo:

fig2


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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
  );
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

O resultado deverá ser igual a esse:

fig3

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>
              );
            }
          })()}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

O resultado dessa alteração será esse:

fig4

 

 

» Search

 

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

fig5.5

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`;
Enter fullscreen mode Exit fullscreen mode

por isso:

let api = `https://rickandmortyapi.com/api/character/?page=${pageNumber}&name=${search}`;
Enter fullscreen mode Exit fullscreen mode

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%; }
}
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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


fig6


 

 

» Pagination

 

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. 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;

Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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

fig7

 

Continua...

Parte 2

Top comments (0)