Temas globais em React JS utilizando Styled Components e Context API
Tags: typescript, react, reactnative, css
Published: 2020-10-10 03:15:31

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](https://dev-to-uploads.s3.amazonaws.com/i/d0xgqg6gsq7xz6ry8f02.PNG)

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: 
```jsx
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:
```jsx
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](https://dev-to-uploads.s3.amazonaws.com/i/e21hd4l97bwiq7pg6rao.PNG)

# 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](https://dev-to-uploads.s3.amazonaws.com/i/9yt3y6zd6ugg4ebionhj.PNG)

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: 
```jsx
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:
```js
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`.
```jsx
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](https://github.com/legeannd/themes-with-styled-components/blob/master/src/components/Button/styles.ts).

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](https://github.com/legeannd/themes-with-styled-components/blob/master/src/styles/global.ts) 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:
```jsx
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](https://dev-to-uploads.s3.amazonaws.com/i/6etf9naeb91jjo65bw1v.PNG)

# 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:
```jsx
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:
```jsx
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](https://github.com/legeannd/themes-with-styled-components/blob/master/src/styles/themes.ts) 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:
```jsx
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:
```jsx
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:
```jsx
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:
```jsx
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:
```js
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:
```json
{
 "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:
```js
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:
```js
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](https://dev-to-uploads.s3.amazonaws.com/i/uy6frjmhm1yl6gu8ts7p.gif)
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](https://github.com/legeannd/themes-with-styled-components).
Espero que este guia tenha sido útil, e que agora você saiba como fazer todos os temas que precisar!