React: contextType e getDerivedStateFromError

Mais duas funcionalidades da v16.6 já disponíveis!

Image for post
Image for post

No artigo anterior sobre a versão 16.6, falamos de React.lazy e React.memo. Hoje, iremos falar de mais duas novidades que foram lançadas e você já pode fazer uso diário:

  • Novo método estáticocontextType, para consumir objetos de contexto
  • Novo ciclo de vida estáticogetDerivedStateFromError, para tratar erros

Como utilizar `static contextType`

É bem comum utilizarmos o padrão Provider para fornecer dados para nossa árvore de componentes.

Vamos recapitular como podemos criar um contexto atualmente:

// ~/Desktop/example/ThemeContext.jsimport * as React from "react";const ThemeContext = React.createContext();const ThemeConsumer = ThemeContext.Consumer;class ThemeProvider extends React.Component {
render() {
return (
<ThemeContext.Provider value={this.props.value}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
export { ThemeProvider, ThemeConsumer, ThemeContext };

E para utilizar esse contexto:

// ~/Desktop/example/main.jsimport * as React from "react";
import * as ReactDOM from "react-dom";
import { ThemeProvider, ThemeConsumer } from "./ThemeContext";class App extends React.Component {
render() {
return (
<ThemeConsumer> // [A]
{theme => {
let mainColor = theme.color.blue["400"];
return <h1 style={{ color: mainColor }}>Hello!</h1>;
}}
</ThemeConsumer>
);
}
}
const theme = {
color: {
blue: {
400: "#00F"
}
}
};
ReactDOM.render(
<ThemeProvider value={theme}>
<App />
</ThemeProvider>,
document.getElementById("root")
);
  • [A]: Precisamos chamar o Context.Consumer explicitamente para termos acesso ao objeto theme, utilizando o famoso render props ou function as children.

Porém, no React v16.6 nós podemos utilizar seu novo método e deixar nosso código um pouco mais simples:

// ~/Desktop/example/main.jsimport * as React from "react";
import * as ReactDOM from "react-dom";
import { ThemeProvider, ThemeContext } from "./ThemeContext";class App extends React.Component {
static contextType = ThemeContext; // [A]
render() {
const mainColor = this.context.color.blue["400"];
return <h1 style={{ color: mainColor }}>Hello!</h1>;
}
}
const theme = {
color: {
blue: {
400: "#00F"
}
}
};
ReactDOM.render(
<ThemeProvider value={theme}>
<App />
</ThemeProvider>,
document.getElementById("root")
);
  • [A]: Temos uma pequena mudança no nosso componente, ao invés de acessarmos o Context.Consumer estamos passando TODO o contexto para static contextType e nossa instancia terá acesso ao objeto injetado pelo contexto em this.context.

REGRA: Com essa API, nós não podemos ter mais de UM contexto por instancia. Para consumir múltiplos contextos, ainda teremos que acessar o .Consumer ou criar uma HOC. Por exemplo:

function withTheme(Component) {
return props => (
<ThemeContext.Consumer>
{theme => <Component theme={theme} {...props} />}
</ThemeContext.Consumer>
);
}
// ...// Utilizando como:
class App extends React.Component {
render() {
const mainColor = this.props.theme.color.blue["400"];
return <h1 style={{ color: mainColor }}>Hello!</h1>;
}
}
// Composição de múltiplos contextos:
return withOutroContext(withTheme(App));

O clico de vida `static getDerivedStateFromError`

No React v16 foi introduzido o conceito de "Error Boundaries", onde um novo ciclo de vida, chamado componentDidCatch foi criado.

Infelizmente, em alguns cenários, componentDidCatch quebra a relação entre componentes pai-filho, pois null é retornado caso a árvore jogue um erro. Esse ciclo de vida também não funciona do lado do servidor, já que método Did não são chamados em SSR.

Por isso, temos o novo método static getDerivedStateFromError onde podemos utilizá-lo da seguinte maneira:

// ~/Desktop/example/erros.jsimport * as React from "react";
import * as ReactDOM from "react-dom";
class CuidandoDosErros extends React.Component {
constructor(props) {
super(props);
this.state = { erro: false };
}
static getDerivedStateFromError(error) {
// Você pode checar o objeto `error` aqui e retornar
// valores diferentes
return { erro: true }; // [A]
}
render() {
if (this.state.erro) {
return <h1>Oops, algo aconteceu :(</h1>;
}
return this.props.children;
}
}
function Outro() {
throw "Opa, um erro ocorreu!"; // [B]
}
class App extends React.Component {
render() {
return (
<CuidandoDosErros>
<Outro />
</CuidandoDosErros>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
  • [A]: Estamos retornando um objeto simples, { erro: true } e no nosso método render estamos criando um fallback para mostrar outro componente ao invés do this.props.children em caso de erro. Esses componentes que lidam com erros podem ser utilizados em qualquer parte da sua árvore de componentes, nesse exemplo, estamos envolvendo o nosso <App /> , mas você pode ser mais específico e criar diferentes retornos por erros.
  • [B]: Jogando um erro na árvore de componentes atual. Esse será o objeto error no nosso item [A].

Dessa maneira, você está cuidando dos erros que sua aplicação joga de uma maneira eficiente e que irá funcionar na renderização no servidor.

Nós também podemos utilizar esse método junto do componentDidCatch. A diferença aqui é sútil, mas podemos ter uma regra: Utilize cDC para comunicar externamente (sua API/Datadog, por exemplo) que um erro ocorreu e utilize gDSFE para comunicar internamente (sua aplicação React) que um erro ocorreu.

Ficando assim:

// ~/Desktop/example/erros-com-log.jsimport * as React from "react";
import * as ReactDOM from "react-dom";
import * as api from './api'class CuidandoDosErros extends React.Component {
constructor(props) {
super(props);
this.state = { erro: false };
}
static getDerivedStateFromError(error) {
// Você pode checar o objeto `error` aqui e retornar
// valores diferentes
return { erro: true };
}
componentDidCatch(error, info) {
// Utilizando o objeto `info` aqui, onde o erro
// é capturado internamente pelo React:
//
// in Outro (created by App)
// in CuidandoDosErros (created by App)
// in App
//
api.logErros(info.componentStack);
}
render() {
if (this.state.erro) {
return <h1>Oops, algo aconteceu :(</h1>;
}
return this.props.children;
}
}
function Outro() {
throw "Opa, um erro ocorreu!";
}
class App extends React.Component {
render() {
return (
<CuidandoDosErros>
<Outro />
</CuidandoDosErros>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

Dessa maneira, estamos implementando um tratamento de erros robusto! Comunicando nossa aplicação interna e externa de que algo ocorreu.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store