DEV Community

loading...
Cover image for React - Bora estilizar?

React - Bora estilizar?

brunolipea profile image Bruno Felipe „ÉĽ10 min read

ūüĎč Introdu√ß√£o

Se você já esteve em algum projeto web com certeza já trabalhou com CSS ou algum dos seus pré-processadores (Sass, LESS, Stylus).
E no React não é diferente. Todas essas tecnologias também podem e devem ser utilizadas, porém vamos começar com o básico e ver como é simples adicionar CSS a um componente.

Primeiro vamos estilizar o Header da nossa aplica√ß√£o, mas para isso teremos que modificar um pouco nosso arquivo. Precisamos criar uma passar com nome Header, assim como o arquivo Header.tsx. E dentro dessa pasta iremos criar um arquivo index.tsx copiando todo conte√ļdo que temos no Header.tsx sem mudar nada. Al√©m disso iremos criar ainda dentro dessa pasta um arquivo styles.css para podermos importar dentro do componente.
Por fim apagamos o Header.tsx.

/* components/Header/styles.css */

#header {
  background: #5636d3;
}
Enter fullscreen mode Exit fullscreen mode
// components/Header/index.tsx

import "./styles.css"

function Header() {
  return (
    <div>
      <header id="header">
        <span>gofinances</span>
        <nav>
          <a href="/">Listagem</a>
          <a href="/create">Cadastro</a>
        </nav>
      </header>
    </div>
  );
}

export default Header
Enter fullscreen mode Exit fullscreen mode

Se o seu servidor ainda estiver rodando (se não, npm start) basta entrar no http://localhost:3000 e você verá que a cor de fundo do nosso componente Header está roxo. Simples assim para utilizar CSS no React.

Caso você vá utilizar algum pré-processador, Google é seu melhor amigo, mas já posso te adiantar que não deve ser nenhum monstro de sete cabeças nem nada do tipo, porque no fim é apenas importar o arquivo CSS para dentro do componente React e ele funcionará.

Mas essa abordagem de criar um arquivo CSS para cada componente tem o seu problema.

‚ĀČÔłŹ Qual o problema?

Ao separarmos os arquivos CSS em cada componente j√° √© algo muito bom, bem melhor do que ter um arquivo √ļnico com toda estiliza√ß√£o da nossa aplica√ß√£o. Por√©m, ai no seu c√≥digo fa√ßa um teste:

  • Duplique a pasta do componente Header e renomeio para um nome qualquer como Header2.
  • Importe esse componente no App.tsx e adicione-o abaixo <Header />.
  • No arquivo styles.css desse novo componente altere o background para uma cor qualquer (pode ser #000 que √© preto).

Se voc√™ acompanhou os √ļltimos posts dessa s√©rie isso deve ser f√°cil para voc√™.

Ao entrar no nosso projeto rodando no browser veremos que os dois Headers tem a mesma cor de fundo, a cor que você alterou. Mas porque isso aconteceu se criamos o CSS para cada componente?

Isso acontece porque estamos utilizando o mesmo seletor CSS para estilizar: #header e o React importa os dois arquivos dentro da p√°gina, assim o CSS que √© carregado por √ļltimo √© o que sobrescreve o outro. Uma solu√ß√£o para isso seria utilizar seletores diferentes, como outro id para o segundo header ou uma classe diferente.
Porém essa abordagem fica bem problemática quando o projeto cresce em escala, já que você não vai lembrar dos nomes que já foram dados anteriormente muito menos saber qual nome outro pessoa que trabalha no projeto utilizou.

E foi pensando nesse caso que vamos utilizar o Styled Components.

ūüďź Componentes Estilizados

O que Styled Components faz √©: ele cria uma classe com identificador √ļnico para cada Componente Estilizado que criamos. Assim ele garante que ao criar um nova estiliza√ß√£o n√£o vamos estar afetando outro lugar da aplica√ß√£o. Ele nada mais √© do que um criador de CSS escrito em Javascript.
Se s√≥ isso n√£o bastasse com ele tamb√©m podemos criar temas para nossa aplica√ß√£o de maneira simples e f√°cil, al√©m de poder criar anima√ß√Ķes reutiliz√°veis entre v√°rios componentes. Mas vamos fazer o simples primeiro.

Vamos instalar o pacote no nosso projeto:

npm i styled-components
Enter fullscreen mode Exit fullscreen mode

Como esse pacote não é escrito em Typescript, temos que instalar também as tipagens. O -D serve para adicionarmos essa dependência apenas para o ambiente de desenvolvimento, já que em produção não estaremos utilizando Typescript.

npm i @types/styled-components -D
Enter fullscreen mode Exit fullscreen mode

:pin: Se você estiver utilizando o VSCode, existe uma extensão que nos ajudará ao trabalhar com o styled-components que é essa. Caso não seja o seu caso, na documentação há ferramentas para outros editores de texto.

Agora, vamos alterar o arquivo styles.css para styles.ts e alterar seu conte√ļdo para:

// components/Header/styles.ts

import styled from 'styled-components'

export const Container = styled.div`
  background: #5636d3;
`
Enter fullscreen mode Exit fullscreen mode

E o que é cada parte desse código?

  • export const Container estamos exportando desse arquivo uma constante que por ser um componente estilizado deve sempre iniciar com letra mai√ļscula.
  • styled √© o nome que damos para a lib, poderia ser batata se quisermos, mas vamos seguir como mostra a documenta√ß√£o.
  • .div indica qual ser√° o retorno desse componente, podendo ser qualquer tag do HTML5.
  • O conte√ļdo dentro das crases s√£o o CSS em si, que ser√° aplicado a essa div utilizando uma classe √ļnica.

‚ö†ÔłŹ Caso essa sintaxe pare√ßa diferente para voc√™ tem um artigo bem legal da Alura que explica como funciona as Tagged Template Literals.

Para vermos o efeito dessa estilização no código temos que alterar o Header/index.tsx:

// components/Header/index.tsx

import { Container } from "./styles";

function Header() {
  return (
    <Container>
      <header>
        <span>gofinances</span>
        <nav>
          <a href="/">Listagem</a>
          <a href="/create">Cadastro</a>
        </nav>
      </header>
    </Container>
  );
}

export default Header
Enter fullscreen mode Exit fullscreen mode

Vemos que o Container é como qualquer outro componente, porém nele contém apenas estilização e nada mais.

A partir de agora temos duas maneiras de prosseguir, sendo elas:

  • Podemos criar v√°rios componentes estilizados dentro de um componente como o Header. Por exemplo, al√©m do Container, outros como Nav, Title e Link cada um estilizando uma parte do nosso componente.
  • Ou podemos utilizar o encadeamento das tags e ir estilizando tudo pelo componente container. Por exemplo, se quisermos que o <span> tenha uma cor de texto branca poder√≠amos fazer assim:
// components/Header/styles.ts

import styled from 'styled-components'

export const Container = styled.div`
  background: #5636d3;

  span {
    color: #FFF
  }
`
Enter fullscreen mode Exit fullscreen mode

Vamos seguir o segundo modelo, por praticidade.

‚ôĽÔłŹ Resetando os estilos globalmente

Muitos das tags do HTML tem uma própria estilização que pode nos atrapalhar. Podemos ver isso no nosso projeto:

Print #1

A tag body tem uma 'margin' para todos os lados de 8px. E para resetar todos essas estiliza√ß√Ķes vamos criar um pasta styles dentro do src. Dentro dessa pasta, vamos criar um arquivo global.ts para nossa estiliza√ß√£o global.

//styles/global.ts

import { createGlobalStyle } from 'styled-components'

export default createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    outline: 0;
    box-sizing: border-box;
  }

  body {
    background: #F0F2F5 ;
    -webkit-font-smoothing: antialiased
  }

  body, input, button {
    font: 16px "Poppins", sans-serif;
  }

  button {
    cursor: pointer;
  }
`
Enter fullscreen mode Exit fullscreen mode

Explicando o que esse arquivo faz:

  • Primeiro importamos de dentro do 'styled-components' uma fun√ß√£o que cria um estiliza√ß√£o global (createGlobalStyle); e assim como faz√≠amos com uma div, escrevemos nosso c√≥digo CSS entre crases.

  • No CSS, estamos zerando todos os espa√ßamentos, mudando a cor de fundo padr√£o e at√© mudando a fonte da aplica√ß√£o. Vale ressaltar a import√Ęncia de ao alterar a fonte, mudar tamb√©m na tag input e button, j√° que em ambos n√£o s√£o aplicados a fonte do body.

  • Por fim, mudamos o cursor padr√£o de um bot√£o para a m√£ozinha do click ūüĎÜ.

Para importamos a fonte 'Poppins' no nosso projeto vamos utilizar o Google Fonts. Como esse processo pode mudar quando você estiver lendo isso vou apenas deixar meu código atual:

<!-- public/index.html -->

<!DOCTYPE html>
<html lang="pt_BR">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap" rel="stylesheet">  
    <title>Go Finances</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Agora temos que importar styles/global.ts no nosso App.tsx para que ele tenha efeito.

Mas antes, só para facilitar mais pra frente vamos passar parte do nosso código que está no App.tsx para outro componente.

Mas porque temos que criar mais um componente? N√£o basta deixar tudo dentro do 'App'?

Lembrando uma das vantagens da componentização é a reutilização de código, e já que teremos mais uma página e em todas queremos que o GlobalStyle seja aplicado temos que separar a nossa página principal. E mais uma vez isso é bem simples.

Basta lembrar, sempre que criamos um compomente que terá estilização criamos uma pasta com o nome do componente e dentro criamos um arquivo index.tsx e outro styles.ts,

Criando uma pasta pages dentro de src, e criando um componente Home que contém:

//pages/Home/index.tsx

import Header from "../../components/Header";
import TransactionList from "../../components/TransactionList";

function Home() {
  return (
    <>
      <Header />
      <TransactionList />
    </>
  );
}

export default Home;
Enter fullscreen mode Exit fullscreen mode

E App.tsx:

import Home from "./pages/Home";
import GlobalStyle from "./styles/global";

function App() {
  return (
    <>
      <GlobalStyle />
      <Home />
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

ūüíĄ Deixando essa p√°gina um pouco mais bonita

Estiliza√ß√£o √© algo que √© muito dif√≠cil de aprender em tutorial. √Č algo que se aprende de tanto fazer e copiar grandes designs. Se voc√™ trabalha com web j√° deve entender pelo menos um pouco sobre Flexbox e/ou Grid Layout, mas se esse n√£o √© o seu caso aconselho a buscar entender um pouco sobre esses modelos de layout depois e por enquanto s√≥ digitar o que eu vou mostrar aqui de estiliza√ß√£o j√° que o foco desses posts √© mostrar o React e n√£o CSS.

Para você que já entende muito bem de CSS, aqui vamos utilizar o básico e praticamente nada de avançado como Media Queries e responsividade, vamos ser o mais básico possível.

Caso não queira copiar todo o código daqui pra frente, basta clonar o projeto apenas com a parte da estilização pronta:

git clone -b entrypoint https://github.com/brunolipe-a/go-finances.git
Enter fullscreen mode Exit fullscreen mode

Mas eu aconselho a você digitar todo o código para fixar.

A estilização do Header fica assim:

//components/Header/styles.ts

import styled from 'styled-components'

export const Container = styled.div`
  background: #5636d3;
  padding: 30px 0;

  header {
    width: 1120px;
    margin: 0 auto;
    padding: 0 20px 150px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: #fff;

    span {
      font-size: 1.75rem;
      font-weight: medium;
    }

    nav {
      a {
        color: #fff;
        text-decoration: none;
        font-size: 16px;
        transition: opacity 0.2s;
        padding-bottom: 6px;
        border-bottom: 2px solid #FF872C;

        & + a {
          margin-left: 32px;
          margin-right: 5px;
        }

        &:hover {
          opacity: 0.6;
        }
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

Crie um arquivo em src/utils/formatValue.ts:

const formatValue = (value: number): string =>
  Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(
    value,
  );

export default formatValue;
Enter fullscreen mode Exit fullscreen mode

Novo componente Cards:

//components/Cards/styles.ts

import styled from 'styled-components';

interface CardProps {
  total?: boolean;
}

export const CardContainer = styled.section`
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 32px;
  margin-top: -150px;
`;

export const Card = styled.div`
  background: ${({ total }: CardProps): string => (total ? '#FF872C' : '#fff')};
  padding: 22px 32px;
  border-radius: 5px;
  color: ${({ total }: CardProps): string => (total ? '#fff' : '#363F5F')};

  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 500;
    a {
      background: #7159c1;
    }
    p {
      font-size: 16px;
    }
  }
  h1 {
    margin-top: 14px;
    font-size: 36px;
    font-weight: 500;
    line-height: 54px;
  }
`;
Enter fullscreen mode Exit fullscreen mode
//components/Cards/index.tsx

import formatValue from "../../utils/formatValue";
import { CardContainer, Card } from "./styles";

export interface Balance {
  income: number;
  outcome: number;
  total: number;
}

type CardsProps = {
  balance: Balance;
};

function Cards({ balance }: CardsProps) {
  return (
    <CardContainer>
      <Card>
        <header>
          <p>Entradas</p>
        </header>
        <h1>{formatValue(balance.income)}</h1>
      </Card>
      <Card>
        <header>
          <p>Saídas</p>
        </header>
        <h1>{formatValue(balance.outcome)}</h1>
      </Card>
      <Card total>
        <header>
          <p>Total</p>
        </header>
        <h1>{formatValue(balance.total)}</h1>
      </Card>
    </CardContainer>
  );
}

export default Cards;
Enter fullscreen mode Exit fullscreen mode

Temos que remover o antigo componente TransactionList e criar novamente utilizando uma pasta:

// components/TransactionList/styles.ts

import styled from 'styled-components';

export const TableContainer = styled.section`
  margin-top: 64px;
  table {
    width: 100%;
    border-spacing: 0 8px;
    th {
      color: #969cb3;
      font-weight: normal;
      padding: 20px 32px;
      text-align: left;
      font-size: 16px;
      line-height: 24px;
    }
    td {
      padding: 20px 32px;
      border: 0;
      background: #fff;
      font-size: 16px;
      font-weight: normal;
      color: #969cb3;
      &.title {
        color: #363f5f;
      }
      &.income {
        color: #12a454;
      }
      &.outcome {
        color: #e83f5b;
      }
    }
    td:first-child {
      border-radius: 8px 0 0 8px;
    }
    td:last-child {
      border-radius: 0 8px 8px 0;
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode
// components/TransactionList/index.tsx

import formatValue from "../../utils/formatValue";

import { TableContainer } from "./styles";

export interface Transaction {
  id: string;
  title: string;
  value: number;
  type: "income" | "outcome";
  category: { title: string };
  created_at: string;
}

type TransactionListProps = {
  transactions: Transaction[];
};

function TransactionList({ transactions }: TransactionListProps) {
  return (
    <TableContainer>
      <table>
        <thead>
          <tr>
            <th>Título</th>
            <th>Preço</th>
            <th>Categoria</th>
            <th>Data</th>
          </tr>
        </thead>

        <tbody>
          {transactions.map((transaction) => (
              <tr key={transaction.id}>
                <td className="title">{transaction.title}</td>
                <td className={transaction.type}>
                  {formatValue(transaction.value)}
                </td>
                <td>{transaction.category?.title}</td>
                <td>
                  {new Date(transaction.created_at).toLocaleDateString("pt-br")}
                </td>
              </tr>
            ))}
        </tbody>
      </table>
    </TableContainer>
  );
}

export default TransactionList;
Enter fullscreen mode Exit fullscreen mode

E no src/pages/Home:

// pages/Home/styles.ts

import styled from 'styled-components';

export const Container = styled.div`
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 40px 20px;
`;
Enter fullscreen mode Exit fullscreen mode
// pages/Home/index.tsx

import { useState } from "react";

import Header from "../../components/Header";

import Cards, { Balance } from "../../components/Cards";
import TransactionList, { Transaction } from "../../components/TransactionList";

import { Container } from "./styles";

function Home() {
  const [balance, setBalance] = useState<Balance>({
    income: 12.12,
    outcome: 6,
    total: 6.12,
  });

  const [transactions, setTransactions] = useState<Transaction[]>([
    {
      id: "1",
      category: { title: "Trabalho" },
      title: "Venda",
      type: "income",
      value: 12.12,
      created_at: "2021-02-03T00:00:00",
    },
    {
      id: "2",
      category: { title: "Comida" },
      title: "Lanche",
      type: "outcome",
      value: 6,
      created_at: "2021-02-21T00:00:00",
    },
  ]);

  return (
    <>
      <Header />
      <Container>
        <Cards balance={balance} />
        <TransactionList transactions={transactions} />
      </Container>
    </>
  );
}

export default Home;
Enter fullscreen mode Exit fullscreen mode

Na Home temos algo novo: useState<Transaction[]>.
Utilizamos <> para informar para o useState qual a tipagem do valor a ser armazenado. Isso ajuda ao desenvolver pois o próprio VSCode vai informar se tentarmos salvar um valor que poderá quebrar código.
Por exemplo, se fossemos salvar uma string seria: useState<string>. Vale lembrar que o valor inicial do estado deve ser do mesmo tipo da tipagem.

ūüėĄ Ficou bonito?

Aqui terminamos a estilização da tela de listagem. Aconselho a digitarem todo o código para guardar na mente como criar componentes estilizados.

No próximo post vamos criar uma API de teste utilizando apenas JSON e começar a consumir essa API para gerar nossa tela dinamicamente.

PS: Um agradecimento especialūüíú ao pessoal da RocketseatūüöÄ pelo conte√ļdo de qualidade em React.

Discussion (0)

pic
Editor guide