DEV Community

Cover image for Temas globais em React JS utilizando Styled Components e Context API
Gean Lucas
Gean Lucas

Posted on

Temas globais em React JS utilizando Styled Components e Context API

Você já criou uma aplicação em React que precisava ter uma mudança dinâmica no esquema de cores, mas não sabia por onde começar? Então, a funcionalidade de temas do Styled Components pode te ajudar! Hoje, iremos fazer um app que irá usar o Theme Provider do Styled Components e a Context API do React pra gerenciar quando mudar o esquema de cores, e o melhor é que ele pode ser aplicado também no React Native quase sem alterações.

Estruturando o projeto

Primeiramente, vamos criar um novo projeto em React utilizando o CRA com Typescript. Para isso, basta executar o seguinte comando:
npx create-react-app meu-app --template typescript
Durante o projeto, eu irei utilizar o Yarn como gerenciador de pacotes, mas você pode utilizar também o NPM sem problemas, basta fazer as adaptações necessárias.

Depois de ter gerado o projeto, vamos apagar alguns arquivos que não serão utilizados durante o desenvolvimento do nosso app, deixando a raiz do projeto com apenas esses arquivos:

Estrutura dos arquivos do projeto

Também serão feitas alterações nos arquivos App.tsx e index.tsx.
No index.tsx, remova as importações desnecessárias, deixando o arquivo com esse conteúdo:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

E no App.tsx, vamos deixar apenas um componente simples como retorno da função, além de remover as importações desnecessárias, deixando o arquivo assim:

import React from 'react';

function App() {
  return (
    <h1>Hello World!</h1>
  );
}

export default App;

Com isso, você já pode rodar o projeto usando yarn start, onde vai ter apenas o nosso Hello World, desse jeito:

Hello World

Desenvolvendo os componentes do projeto

Agora, vamos começar de fato com o desenvolvimento do nosso app, e pra isso, precisamos instalar a lib Styled Components pra podermos prosseguir. Instale-a utilizando o comando yarn add styled-components, e também instalar a tipagem da lib como dependência de desenvolvimento com através do comando yarn add -D @types/styled-components.

Como o foco deste artigo não é falar sobre estilização em si, vou apenas criar um componente de botão, que servirá perfeitamente pra demonstrar a aplicação dos temas. Para criar o componente, crie uma pasta components dentro da pasta src, e dentro dela crie o nosso componente. Ao final, teremos a seguinte estrutura de arquivos:

Estruturação das pastas com o componente de botão

Dentro do nosso arquivo index.tsx, iremos criar um FC para utilizar dentro do App.tsx, e iremos criar no styles.ts a estilização necessária. Com isso, o index.tsx do Button ficará assim:

import React from 'react';

import { Container } from './styles';

const Button: React.FC = () => {
  return (
    <Container>
      <h1>Button</h1>
    </Container>
  );
};

export default Button;

E o styles.ts ficará assim:

import styled from 'styled-components';

export const Container = styled.div`

`;

Agora, vamos importar este componente lá na raíz do nosso projeto, no arquivo App.tsx.

import React from 'react';
import Button from './components/Button';

function App() {
  return (
    <Button />
  );
}

export default App;

Com isso, o nosso componente Button já está aparecendo na página inicial, mas ele ainda não está parecendo com um botão de fato, então vamos partir pra estilização. Vou deixar um link pro arquivo de estilização no repositório do projeto, pra que você não gaste muito tempo com isso, então clique aqui pra acessar.

Para que a gente possa ver as alterações nas cores do projeto, é necessário ter uma estilização no App.tsx, então vou deixar aqui o link para o arquivo de estilos globais que iremos usar. Então, crie um arquivo global.ts com essa estilização dentro de uma pasta styles pra que possa ser facilmente acessível. Depois de importar os estilos globais, nosso App.tsx vai estar assim:

import React from 'react';
import Button from './components/Button';

import GlobalStyle from './styles/global';

function App() {
  return (
    <>
      <GlobalStyle />

      <h1>App super completo</h1>

      <Button />
    </>
  );
}

export default App;

Com isso, essa deve ser a aparência do nosso app até agora:

Alt Text

Criando o provedor de temas

Agora que a gente já tem esse aplicativo digno de ganhar os maiores prêmios de UX e UI, podemos partir pra parte de mudar o tema de fato.

O primeiro passo será criar uma função que será chamada toda vez que o botão for clicado. Vamos chamar ela de handleToggleTheme, já que ela será responsável por trocar o tema. Por enquanto, ela terá apenas um console.log pra mostrar que está sendo chamada corretamente. Além disso, precisamos chamar ela no método onClick do componente. Por equanto, nosso index.tsx do botão ficará assim:

import React from 'react';

import { Container, ComponentButton } from './styles';

const Button: React.FC = () => {
  const handleToggleTheme = () => {
    console.log('O botão foi clicado');
  }

  return (
    <Container>
      <ComponentButton onClick={handleToggleTheme} >Clique aqui para mudar o tema</ComponentButton>
    </Container>
  );
};

export default Button;

Agora, podemos seguir com a criação do nosso contexto, que vai ficar responsável por gerenciar o tema do app. Para isso, vamos criar uma pasta hooks que terá apenas um arquivo theme.tsx, que cuidará do contexto.

Após fazermos a importação do React dentro do arquivo, vamos criar uma interface Theme, que terá a tipagem das cores que iremos utilizar, além de um nome servirá para identificar cada tema. Cada cor que utilizamos na estilização do botão e do app será uma propriedade. Também precisamos criar uma interface ThemeContextData, que conterá o nosso tema atual, além da função responsável por trocá-lo.
Com as interfaces criadas, podemos criar de fato o nosso contexto de tema, passando como tipagem as interfaces que a gente criou agora, e sendo iniciado com um objeto vazio. Também vamos criar o nosso hook useTheme, que poderemos chamar em qualquer lugar pra trocar o tema. Por enquanto, nosso hook está assim:

import React, { createContext, useContext } from 'react';

interface ThemeContextData {
  toggleTheme(): void;
  theme: Theme;
}

interface Theme {
  name: string;
  colors: {
    primary: string,
    black: string,
    background: string,
    border: string,
  }
}

const ThemeContext = createContext<ThemeContextData>({} as ThemeContextData);

export const useTheme = () => useContext(ThemeContext);

Agora que a gente já criou a interface dos nossos temas, vamos criá-los. Para isso, crie um arquivo themes.ts dentro da nossa pasta styles. Eu irei criar apenas dois temas, o firstTheme e o secondTheme, mas você pode criar quantos quiser, basta fazer as adaptações necessárias. Para acelerar nosso desenvolvimento, vou deixar o link para meu arquivo de temas com as cores já escolhidas, então clique aqui para acessar os temas que eu criei.

O próximo passo é criar um Provider pro nosso contexto, dentro do nosso arquivo theme.tsx. O provider é o componente que será chamado no começo do app, e irá englobar a todos os seus componentes, possibilitando que todos os filhos tenham as propriedades passadas pelo Provider. Nosso Provider terá um estado, que armazenará o tema que a nós estivermos usando, e para que já tenha alguma cor quando abrirmos o app, vamos importar os temas que a gente criou, e iniciar o estado com o nosso firstTheme.
Agora, vamos criar a função toggleTheme que declaramos lá em cima. Vou criá-la como um callback, por questões de performance, mas também vai funcionar como uma função padrão ou uma arrow function. A toggleTheme será responsável por verificar o tema que está aplicado atualmente, e trocá-lo pelo outro tema que nós temos disponível. Por enquanto, nosso CustomThemeProvider está assim:

export const CustomThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<Theme>(firstTheme);

  const toggleTheme = useCallback(() => {
    if (theme.name === 'first'){
      setTheme(secondTheme);
    }
    else if (theme.name === 'second') {
      setTheme(firstTheme);
    }
  }, [theme]);
}

Agora, o último passo é definir o retorno do nosso CustomThemeProvider. Pra gerenciarmos os temas de maneira simples, vamos utilizar o ThemeProvider do Styled Components. Com isso, basta retorná-lo como filho do nosso Provider, passando o tema armazenado no nosso estado como sua propriedade, e então ele vai gerenciar automaticamente as cores em qualquer lugar que elas estejam sendo utilizadas. Também precisamos passar como valores do nosso Provider a função responsável por mudar o tema, e o próprio tema. Com isso, o nosso arquivo theme.tsx terá o seguinte formato:

import React, { createContext, useCallback, useContext, useState } from 'react';
import { ThemeProvider } from 'styled-components';
import { firstTheme, secondTheme } from '../styles/themes';

interface ThemeContextData {
  toggleTheme(): void;
  theme: Theme;
}

interface Theme {
  name: string;
  colors: {
    primary: string,
    black: string,
    background: string,
    border: string,
  }
}

const ThemeContext = createContext<ThemeContextData>({} as ThemeContextData);

export const useTheme = () => useContext(ThemeContext);

export const CustomThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<Theme>(firstTheme);

  const toggleTheme = useCallback(() => {
    if (theme.name === 'first'){
      setTheme(secondTheme);
    }
    else if (theme.name === 'second') {
      setTheme(firstTheme);
    }
  }, [theme]);

  return (
    <ThemeContext.Provider
      value={{ toggleTheme, theme }}
    >
      <ThemeProvider theme={theme}>
        {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  )
}

export default ThemeProvider;

Aplicando os temas

Agora que já finalizamos a construção do nosso ThemeProvider, vamos aplicá-lo na raiz dos nossos componentes, para que ele possa mudar as cores conforme o necessário. Para isso, vamos no arquivo App.tsx, e vamos transformar aquele Fragment no nosso CustomThemeProvider, pra que ele tenha acesso às cores em toda a nossa aplicação. Com isso, nosso App.tsx vai ficar assim:

import React from 'react';
import Button from './components/Button';
import { CustomThemeProvider } from './hooks/theme';

import GlobalStyle from './styles/global';

function App() {
  return (
    <CustomThemeProvider>
      <GlobalStyle />

      <h1>App super completo</h1>

      <Button />
    </CustomThemeProvider>
  );
}

export default App;

E agora, vamos mudar o nosso componente Button para que ele chame a função toggleTheme quando for clicado. Para isso, vamos importar o hook useTheme que a gente criou, e pegar a função toggleTheme, e chamar ela dentro da função handleToggleTheme, que está sendo chamada a cada clique do botão. Com isso, nosso componente de botão ficou assim:

import React from 'react';
import { useTheme } from '../../hooks/theme';

import { Container, ComponentButton } from './styles';

const Button: React.FC = () => {
  const { toggleTheme } = useTheme();

  const handleToggleTheme = () => {
    toggleTheme();
  }

  return (
    <Container>
      <ComponentButton onClick={handleToggleTheme} >Clique aqui para mudar o tema</ComponentButton>
    </Container>
  );
};

export default Button;

No entanto, se você clicar no botão agora, nada vai acontecer, pois a gente colocou as cores estaticamente na nossa aplicação. Então, para que as cores possam ser mudadas, vamos nos arquivos de estilos, mudar a forma como as cores são obtidas.
Primeiro, precisamos declarar pro Styled Components o tipo do nosso tema. Pra isso, vamos criar um arquivo styled-components.d.ts dentro de uma pasta @types. A declaração pode ser feita da seguinte maneira:

import { firstTheme } from '../styles/themes';

type CustomTheme = typeof firstTheme;

declare module 'styled-components' {
  export interface DefaultTheme extends CustomTheme {}
}

E no arquivo tsconfig.json, adicione o nome do arquivo que acabamos de criar dentro do include, para que o transpilador possa reconhecer corretamente:

{
 "compilerOptions": {
...
}
"include": [
    "src",
    "styled-components.d.ts"
  ]
}

Depois de declarar pro Styled Components o tipo do nosso tema, vamos no arquivo global.ts mudar as cores de lá. O primeiro passo, é saber que com Styled Components, todas as propriedades que podem ser passadas para um componente, estão disponíveis através das chamadas props, que podem ser acessadas no arquivo de estilos como uma propriedade é acessada normalmente em react: ${props => props.etc}.
Os temas ficam dentro de uma propriedade props.theme, que é o tema atual da aplicação. Como as cores do nosso tema estão dentro de uma propriedade chamada colors, precisamos acessà-las da maneira correta. Como criamos aquele arquivo de declaração de tipos pro nosso tema, as propriedades do tema já estarão disponíveis pra uso. Portanto, alterando as cores, o nosso arquivo global.ts ficará assim:

import { createGlobalStyle } from 'styled-components';

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

  button {
    cursor: pointer;
  }

  body {
    width: 100%;
    background: ${props => props.theme.colors.primary};
  }

  h1 {
    width: 100%;
    margin: 50px 0;
    text-align: center;
    color: ${props => props.theme.colors.black};
  }
`;

E o nosso styles.ts dentro do componente Button, ficará assim:

import styled from 'styled-components';

export const Container = styled.div`
  margin-left: 42%;
`;

export const ComponentButton = styled.button`
  width: 300px;
  height: 100px;
  font-size: 30px;
  background: ${props => props.theme.colors.background};
  color: ${props => props.theme.colors.black};
  border: 5px solid ${props => props.theme.colors.border};
  border-radius: 5px;
`;

Com isso, já temos nosso app funcionando com a mudança de temas.
Aplicativo com mudança de temas dinamicamente
Você pode ver que ao clicar no botão, todas as cores que passamos como props foram alteradas, o que dá muitas possibilidades para a estilização de sistemas. Como a Context API é um recurso nativo, o mesmo hook que criamos aqui pode ser aplicado também no React Native, fazendo as devidas alterações. Caso queira ver o repositório do projeto completo, clique aqui.
Espero que este guia tenha sido útil, e que agora você saiba como fazer todos os temas que precisar!

Top comments (1)

Collapse
 
guicalmeida profile image
Guilherme Almeida

Gean, MUITO obrigado. Passei as últimas muitas horas tentando descobrir como alterar o tema do ThemeProvider por meio de um componente - nem me ocorreu criar um hook encapsulando ele. Se você estiver com mais paciência, eu adoraria entender melhor o que exatamente você fez no trecho const ThemeContext = createContext<ThemeContextData>({} as ThemeContextData) e porque do uso do hook useCallback

Por último, só uma pequena correção: você exportou o ThemeProvider e não o CustomThemeProvider do theme.tsx 😅

novamente, muito obrigado