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.
Nosso primeiro passo agora é criar uma estrutura de pasta para organizar o app.
Crie uma estrutura de pastas como essa abaixo:
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:
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:
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:
» Search
O próximo passo será a criação do componente Search para pesquisar o card pelo termo digitado conforme a figura abaixo:
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)
__________________________________________________________________
##
##
##» 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](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)
##
Continua...
[Parte 2](https://dev.to/dooramos/react-js-construindo-uma-wiki-de-personagens-ricky-e-morty-parte-2-51dm)
Top comments (0)