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

dooramos

Eduardo Ramos

Posted on July 27, 2022

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

 

 

» Filter

 

Bora lá colocar filtros...

Primeiramente vamos criar uma estrutura de pasta e arquivos.
Dentro da pasta Filter teremos os arquivos: Filter.js e FilterBTN.js. Teremos também uma pasta category e dentro dela os arquivos: Gender.js, Species.js e Status.js

No arquivo App.js vamos criar mais alguns hooks para armazenar os filtros. Também vamos precisar mudar a url da api novamente, para receber os novos parâmetros.

Adicione na função App() esse código abaixo:

let [status, updateStatus] = useState("");
let [gender, updateGender] = useState("");
let [species, updateSpecies] = useState("");
Enter fullscreen mode Exit fullscreen mode

e altere a api para:

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

Feito isso, substitua o "Filtro aparecerá aqui" por:

<Filter
  pageNumber={pageNumber}
  status={status}
  updateStatus={updateStatus}
  updateGender={updateGender}
  updateSpecies={updateSpecies}
  updatePageNumber={updatePageNumber}
/>
Enter fullscreen mode Exit fullscreen mode

No arquivo Filter.js vamos importar as categorias criadas:

import React from "react";
import Gender from "./category/Gender";
import Species from "./category/Species";
import Status from "./category/Status";

Enter fullscreen mode Exit fullscreen mode

Antes que eu me esqueça vou colocar o código de cada arquivo das categorias aqui, começando pelo Gender.js:

import React from "react";
import FilterBTN from "../FilterBTN";

const Gender = ({ updateGender, updatePageNumber }) => {
  let genders = ["female", "male", "genderless", "unknown"];
  return (
    <div className="accordion-item">
      <h2 className="accordion-header" id="headingThree">
        <button
          className="accordion-button collapsed"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#collapseThree"
          aria-expanded="false"
          aria-controls="collapseThree"
        >
          Gender
        </button>
      </h2>
      <div
        id="collapseThree"
        className="accordion-collapse collapse"
        aria-labelledby="headingThree"
        data-bs-parent="#accordionExample"
      >
        <div className="accordion-body d-flex flex-wrap gap-3">
          {genders.map((items, index) => {
            return (
              <FilterBTN
                name="gender"
                index={index}
                key={index}
                updatePageNumber={updatePageNumber}
                task={updateGender}
                input={items}
              />
            );
          })}
        </div>
      </div>
    </div>
  );
};

export default Gender;

Enter fullscreen mode Exit fullscreen mode

Agora o Species.js:

import React from "react";
import FilterBTN from "../FilterBTN";

const Species = ({ updateSpecies, updatePageNumber }) => {
  let species = [
    "Human",
    "Alien",
    "Humanoid",
    "Poopybutthole",
    "Mythological",
    "Unknown",
    "Animal",
    "Disease",
    "Robot",
    "Cronenberg",
    "Planet",
  ];
  return (
    <div className="accordion-item ">
      <h2 className="accordion-header" id="headingTwo">
        <button
          className="accordion-button collapsed"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#collapseTwo"
          aria-expanded="false"
          aria-controls="collapseTwo"
        >
          Species
        </button>
      </h2>
      <div
        id="collapseTwo"
        className="accordion-collapse collapse"
        aria-labelledby="headingTwo"
        data-bs-parent="#accordionExample"
      >
        <div className="accordion-body d-flex flex-wrap gap-3">
          {species.map((item, index) => {
            return (
              <FilterBTN
                name="species"
                index={index}
                key={index}
                updatePageNumber={updatePageNumber}
                task={updateSpecies}
                input={item}
              />
            );
          })}
        </div>
      </div>
    </div>
  );
};

export default Species;

Enter fullscreen mode Exit fullscreen mode

E por ultimo o Status.js

import React from "react";
import FilterBTN from "../FilterBTN";

const Status = ({ updateStatus, updatePageNumber }) => {
  let status = ["Alive", "Dead", "Unknown"];
  return (
    <div className="accordion-item">
      <h2 className="accordion-header" id="headingOne">
        <button
          className="accordion-button"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#collapseOne"
          aria-expanded="true"
          aria-controls="collapseOne"
        >
          Status
        </button>
      </h2>
      <div
        id="collapseOne"
        className="accordion-collapse collapse show"
        aria-labelledby="headingOne"
        data-bs-parent="#accordionExample"
      >
        <div className="accordion-body d-flex flex-wrap gap-3">
          {status.map((item, index) => (
            <FilterBTN
              key={index}
              index={index}
              name="status"
              task={updateStatus}
              updatePageNumber={updatePageNumber}
              input={item}
            />
          ))}
        </div>
      </div>
    </div>
  );
};

export default Status;

```



Vamos deixar o arquivo **FilterBTN.js** pronto também com o seguinte código:



```
import React from "react";

const FilterBTN = ({ input, task, updatePageNumber, index, name }) => {
  return (
    <div>
      <style jsx>
        {`
          .x:checked + label {
            background-color: #0b5ed7;
            color: white;
          }
          input[type="radio"] {
            display: none;
          }
        `}
      </style>

      <div className="form-check">
        <input
          className="form-check-input x"
          type="radio"
          name={name}
          id={`${name}-${index}`}
        />
        <label
          onClick={(x) => {
            task(input);
            updatePageNumber(1);
          }}
          className="btn btn-outline-primary"
          for={`${name}-${index}`}
        >
          {input}
        </label>
      </div>
    </div>
  );
};

export default FilterBTN;

```



Voltando ao arquivo **Filter.js** que até esse ponto possui apenas 4 linhas de código. Vamos deixá-lo assim:



```
import React from "react";
import Gender from "./category/Gender";
import Species from "./category/Species";
import Status from "./category/Status";

const Filter = ({
  pageNumber,
  updatePageNumber,
  updateStatus,
  updateGender,
  updateSpecies,
}) => {
  let clear = () => {
    updateStatus("");
    updateGender("");
    updateSpecies("");
    updatePageNumber(1);
    window.location.reload(false);
  };
  return (
    <div className="col-lg-3 col-12 mb-5">
      <div className="text-center fw-bold fs-4 mb-2">Filtros</div>
      <div
        style={{ cursor: "pointer" }}
        onClick={clear}
        className="text-primary text-decoration-underline text-center mb-3"
      >
        Limpar Filtros
      </div>
      <div className="accordion" id="accordionExample">
        <Status
          updatePageNumber={updatePageNumber}
          updateStatus={updateStatus}
        />
        <Species
          updatePageNumber={updatePageNumber}
          updateSpecies={updateSpecies}
        />
        <Gender
          updatePageNumber={updatePageNumber}
          updateGender={updateGender}
        />
      </div>
    </div>
  );
};

export default Filter;

```



Pra finalizar e testar os filtros o **App.js** tem que estar assim:


Enter fullscreen mode Exit fullscreen mode

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

function App() {

let [pageNumber, updatePageNumber] = useState(1);
let [status, updateStatus] = useState("");
let [gender, updateGender] = useState("");
let [species, updateSpecies] = useState("");
let [fetchedData, updateFetchedData] = useState([]);
let [search, setSearch] = useState("");
let { info, results } = fetchedData;

let api = https://rickandmortyapi.com/api/character/?page=${pageNumber}&name=${search}&status=${status}&gender=${gender}&species=${species};
useEffect(() => {
(async function () {
let data = await fetch(api).then((res) => res.json());
updateFetchedData(data);
})();
}, [api]);

return (


Personagens

<Search setSearch={setSearch} updatePageNumber={updatePageNumber} />

<div className="container">
<div className="row">
<Filter
        pageNumber={pageNumber}
        status={status}
        updateStatus={updateStatus}
        updateGender={updateGender}
        updateSpecies={updateSpecies}
        updatePageNumber={updatePageNumber}
      />
  <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}
  />

);
}

export default App;




Agora vamos criar o componente com o menu de navegação.

##&nbsp;
##&nbsp;
##&raquo; Navigation
##&nbsp;

![fig8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s76g5e5lgp7wk0xipsfj.png)


Dentro da pasta **_src_**, crie uma pasta **_Pages_** e dentro dela crie 2 arquivos **Episodes.js** e **Location.js**.

Agora vá para o arquivo **App.js** e importe esses arquivos que acabamos de criar:


Enter fullscreen mode Exit fullscreen mode

import Episodes from "./Pages/Episodes";
import Location from "./Pages/Location";



Vamos adicionar o react-router-dom e importar alguns componentes:


Enter fullscreen mode Exit fullscreen mode

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";




Agora vamos criar uma função auxiliar dentro do **App.js** mesmo:


Enter fullscreen mode Exit fullscreen mode

const Home = () => {
// Código aqui
}




Dentro da função **App** vamos criar um **Router** e um **Navbar** igual o código abaixo:


Enter fullscreen mode Exit fullscreen mode

function App() {
return (





);
}



Agora definimos nossas rotas. Cada rota requer o caminho (path) e o componente (element):


Enter fullscreen mode Exit fullscreen mode


} />
} />
} />




Feito isso, vamos criar o código da nossa **Navbar.js**.
Dentro do pasta **_Navbar_** crie um arquivo chamado **Navbar.js** e coloque o código abaixo:


Enter fullscreen mode Exit fullscreen mode

import React from "react";
import { NavLink, Link } from "react-router-dom";
import "../../App.scss";

const Navbar = () => {
return (



Rick e Morty WiKi

{<code><br> button[aria-expanded=&quot;false&quot;] &gt; .close {<br> display: none;<br> }<br> button[aria-expanded=&quot;true&quot;] &gt; .open {<br> display: none;<br> }<br> </code>}
className="navbar-toggler border-0"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup"
aria-expanded="false"
aria-label="Toggle navigation"
>



className="collapse navbar-collapse justify-content-end"
id="navbarNavAltMarkup"
>


Personagens


Episódios

activeClassName="active"
className="nav-link"
to="/location"
>
Localizações





);
};

export default Navbar;




Vamos criar mais páginas para testar a navegação. Porém antes de criarmos as próximas páginas, vamos criar um componente para auxiliar no filtro, que será incluído nelas. 
Esse componente terá o papel similar aos filtros de categoria, logo vamos criá-lo dentro da pasta **_category_** que está dentro da pasta **_Filter_**. Crie um arquivo chamado **InputGroup.js**

Coloque o código abaixo:


Enter fullscreen mode Exit fullscreen mode

import React from "react";

const InputGroup = ({ name, changeID, total }) => {
return (


onChange={(e) => changeID(e.target.value)}
className="form-select"
id={name}
>
Escolha...
{[...Array(total).keys()].map((x, index) => {
return (

{name} - {x + 1}

);
})}


);
};

export default InputGroup;




Dentro de **Episode.js** que está na pasta **_Pages_**, coloque o código abaixo:


Enter fullscreen mode Exit fullscreen mode

import React, { useEffect, useState } from "react";
import Card from "../components/Card/Card";
import InputGroup from "../components/Filter/category/InputGroup";

const Episodes = () => {
let [results, setResults] = React.useState([]);
let [info, setInfo] = useState([]);
let { air_date, episode, name } = info;
let [id, setID] = useState(1);

let api = https://rickandmortyapi.com/api/episode/${id};

useEffect(() => {
(async function () {
let data = await fetch(api).then((res) => res.json());
setInfo(data);

  let a = await Promise.all(
    data.characters.map((x) => {
      return fetch(x).then((res) => res.json());
    })
  );
  setResults(a);
})();
Enter fullscreen mode Exit fullscreen mode

}, [api]);

return (




Episode name :{" "}
{name === "" ? "Unknown" : name}



Air Date: {air_date === "" ? "Unknown" : air_date}




Episódios











);
};

export default Episodes;



O que foi feito aqui é uma página com uma nova url para a api, onde o combo representado pelo componente **InputGroup** filtra o resultado.

No arquivo **Location.js** coloque este código:


Enter fullscreen mode Exit fullscreen mode

import React, { useEffect, useState } from "react";
import Card from "../components/Card/Card";
import InputGroup from "../components/Filter/category/InputGroup";

const Location = () => {
let [results, setResults] = React.useState([]);
let [info, setInfo] = useState([]);
let { dimension, type, name } = info;
let [number, setNumber] = useState(1);

let api = https://rickandmortyapi.com/api/location/${number};

useEffect(() => {
(async function () {
let data = await fetch(api).then((res) => res.json());
setInfo(data);

  let a = await Promise.all(
    data.residents.map((x) => {
      return fetch(x).then((res) => res.json());
    })
  );
  setResults(a);
})();
Enter fullscreen mode Exit fullscreen mode

}, [api]);

return (




Location :

{" "}
{name === "" ? "Unknown" : name}



Dimension: {dimension === "" ? "Unknown" : dimension}

Type: {type === "" ? "Unknown" : type}




Localização










);
};

export default Location;



Perceba que fizemos a mesma coisa aqui, trocando obviamente o _episodes_ por _location_ e alterando a url da api.


Agora vamos conferir mais uma vez o arquivo **App.js** que deve estar igual o código abaixo:


Enter fullscreen mode Exit fullscreen mode

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

import Search from "./components/Search/Search";
import Card from "./components/Card/Card";
import Pagination from "./components/Pagination/Pagination";
import Filter from "./components/Filter/Filter";
import Navbar from "./components/Navbar/Navbar";

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Episodes from "./Pages/Episodes";
import Location from "./Pages/Location";

function App() {
return (





} />

    <Route path="/episodes" element={<Episodes />} />


    <Route path="/location" element={<Location />} />

  </Routes>
</Router>
Enter fullscreen mode Exit fullscreen mode

);
}
const Home = () => {
let [pageNumber, updatePageNumber] = useState(1);
let [status, updateStatus] = useState("");
let [gender, updateGender] = useState("");
let [species, updateSpecies] = useState("");
let [fetchedData, updateFetchedData] = useState([]);
let [search, setSearch] = useState("");
let { info, results } = fetchedData;

let api = https://rickandmortyapi.com/api/character/?page=${pageNumber}&name=${search}&status=${status}&gender=${gender}&species=${species};

useEffect(() => {
(async function () {
let data = await fetch(api).then((res) => res.json());
updateFetchedData(data);
})();
}, [api]);
return (


Personagens





pageNumber={pageNumber}
status={status}
updateStatus={updateStatus}
updateGender={updateGender}
updateSpecies={updateSpecies}
updatePageNumber={updatePageNumber}
/>







info={info}
pageNumber={pageNumber}
updatePageNumber={updatePageNumber}
/>

);
};

export default App;




Sua aplicação  deve estar similar a isso:
![fig9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9is0rldza8zerq0rgx1e.png)

_______________

![fig10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ft15j9u24jx4dd41bn0l.png)

___________________ 

![fig11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j0rogy3ijdpln4zj59i0.png)

___________________

Antes de finalizar vamos criar uma página/componente para exibir detalhes do card que foi clicado.
Será uma página simples apenas com alguns detalhes a mais.
Futuramente iremos implementar uma modal, na evolução deste artigo, mas por ora optamos pela simplicidade de uma página comum.

Vá até a pasta **_Card_** e crie um arquivo chamado **CardDetails.js**.

Nele coloque o seguinte código:


Enter fullscreen mode Exit fullscreen mode

import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";

const CardDetails = () => {
let { id } = useParams();

let [fetchedData, updateFetchedData] = useState([]);
let { name, location, origin, gender, image, status, species } = fetchedData;

let api = https://rickandmortyapi.com/api/character/${id};

useEffect(() => {
(async function () {
let data = await fetch(api).then((res) => res.json());
updateFetchedData(data);
})();
}, [api]);

return (



{name}

    <img className="img-fluid" src={image} alt="" />
    {(() => {
      if (status === "Dead") {
        return <div className="badge bg-danger fs-5">{status}</div>;
      } else if (status === "Alive") {
        return <div className=" badge bg-success fs-5">{status}</div>;
      } else {
        return <div className="badge bg-secondary fs-5">{status}</div>;
      }
    })()}
    <div className="content">
      <div className="">
        <span className="fw-bold">Gender : </span>
        {gender}
      </div>
      <div className="">
        <span className="fw-bold">Location: </span>
        {location?.name}
      </div>
      <div className="">
        <span className="fw-bold">Origin: </span>
        {origin?.name}
      </div>
      <div className="">
        <span className="fw-bold">Species: </span>
        {species}
      </div>
    </div>
  </div>
</div>

);
};

export default CardDetails;



Aqui estamos criando um card, a partir de um id que foi selecionado previamente no momento do click.
A api retorna alguns dados de acordo com o id que foi informado para ela. Dessa forma conseguimos ter nossa página de detalhes funcionando.

Vamos finalizar ajustando o arquivo **App.js** para redirecionar corretamente para a rota de detalhes. Para isso inclua mais algumas rotas:
`<Route path="/:id" element={<CardDetails />} />`
`<Route path="/episodes/:id" element={<CardDetails />} />`
`<Route path="/location/:id" element={<CardDetails />} />` 

Dentro do arquivo **Card.js** vamos colocar um elemento Link para encapsular a div e tornar o card "clicável". Para facilitar vou deixar aqui o código atualizado do **Card.js**:


import React from "react";
import { Link } from "react-router-dom";
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 (
    <Link
      style={{ textDecoration: "none" }}
      to={`${page}${id}`}
      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>
          );
        }
      })()}
    </Link>
  );
});

} else {
display = "Nenhum personagem encontrado :/";
}

return <>{display}</>;
};

export default Card;




Importe o componente **CardDetails.js** dentro do **App.js**:
`import CardDetails from "./components/Card/CardDetails";`

E pra não ter erro o seu arquivo **App.js** deverá ficar assim:



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

import Search from "./components/Search/Search";
import Card from "./components/Card/Card";
import CardDetails from "./components/Card/CardDetails";
import Pagination from "./components/Pagination/Pagination";
import Filter from "./components/Filter/Filter";
import Navbar from "./components/Navbar/Navbar";

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Episodes from "./Pages/Episodes";
import Location from "./Pages/Location";

function App() {
return (





} />
} />
} />
} />
} />
} />


);
}
const Home = () => {
let [pageNumber, updatePageNumber] = useState(1);
let [status, updateStatus] = useState("");
let [gender, updateGender] = useState("");
let [species, updateSpecies] = useState("");
let [fetchedData, updateFetchedData] = useState([]);
let [search, setSearch] = useState("");
let { info, results } = fetchedData;

let api = https://rickandmortyapi.com/api/character/?page=${pageNumber}&name=${search}&status=${status}&gender=${gender}&species=${species};

useEffect(() => {
(async function () {
let data = await fetch(api).then((res) => res.json());
updateFetchedData(data);
})();
}, [api]);
return (


Personagens





pageNumber={pageNumber}
status={status}
updateStatus={updateStatus}
updateGender={updateGender}
updateSpecies={updateSpecies}
updatePageNumber={updatePageNumber}
/>







info={info}
pageNumber={pageNumber}
updatePageNumber={updatePageNumber}
/>

);
};

export default App;




O resultado final ao clicar no card deverá ser igual a figura abaixo: 

![fig13](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzvijrb2eulump05cpak.png)


_____________________

Esse foi um post para mostrar o básico de **React**. 
Resolvi fazer esse post, pois acompanhei algumas postagens e tutoriais que "ensinavam" a implementar o consumo dessa mesma api inclusive, porém sempre me senti perdido quanto a didática da postagem, pois não ficava claro onde colocar arquivos ou trecho de códigos. Pra quem está iniciando é bem complicado quando não se consegue acompanhar ou reproduzir o artigo ou tutorial. Tentei ser o mais claro e objetivo.
Em breve escreverei outros artigos incluindo **Typescript**, **Context** e outros conceitos. Espero que gostem e principalmente consigam entender e implementar. 






##&nbsp;
_____________________

_____________________[Clique aqui para ver o resultado final](https://wiki-rick-morty-react.vercel.app/)_____________________

_____________________
##&nbsp;
A primeira parte do artigo está aqui: 

[Parte 1](https://dev.to/dooramos/react-js-construindo-uma-wiki-de-personagens-ricky-e-morty-parte-1-4g9b)
💖 💪 🙅 🚩
dooramos
Eduardo Ramos

Posted on July 27, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related