Comunicação entre componentes React usando Typescript, Hooks e sem dor

Tags: typescript, react, hooks, javascript
Published: 2020-08-01 17:29:37

Se você está apenas desesperado pela resposta, vá para o parágrafo 6. Mas se você, assim como eu, também teve aquele ligeiro surto e também pensou em desistir do typescript ao se deparar com o _vscode_ alertando sobre ~~muitos~~ alguns erros em seu código em decorrência da tipagem apenas porque você queria chamar um método de outro componente, acompanhe essa leitura e veja que resolver tal problema pode ser bem mais simples do que você imagina.

## Breve intro
Antes de tudo, devo considerar que alguns leitores não estão habituados com as palavras-chave do texto, então aí vai uma breve introdução mastigadinha:

* **Componente:** Um componente pode ser descrito simplesmente como um objeto. Possui métodos, atributos e estado próprio e pode se relacionar ou ser relacionado com outros componentes. Em outras palavras, é uma partezinha do sistema que pode ser reutilizada;
* **React:** É um framework Javascript implementado para criar interfaces de usuário de forma interativa e reativa. É utilizado para a criação e manipulação de componentes;
* **Typescript:** É um superconjunto do Javascript que permite tipagem estática, orientação a objetos e facilita a escrita de um código de fácil legibilidade;
* **Hooks:** Uma alternativa às classes. Usados para definir estado de um objeto, referência, efeito imediato além de vários outros artifícios que propiciam uma implementação menos tortuosa.

## Comunicação
A comunicação entre componentes é de suma importância para o bom reuso de código, além de facilitar a leitura e apresentar uma melhor resolução de possíveis problemas. Entretanto, principalmente para quem está começando e até mesmo para quem tem alguma experiência, essa interlocução de objetos com algum parentesco pode se tornar um pouco mais nebulosa ao se deparar com milhares de possíveis soluções encontradas na internet.

_Antes de começar, eu gostaria de externar minha infelicidade pois 95% dessas possíveis soluções eram baseadas em componentes construídos a partir de classes, o que já deixava meu estresse momentâneo bem mais inflamado._

Mas vamos deixar o estresse de lado, pois esse post é só alegria.

### Componente _Filho_ utilizando métodos ou atributos de um Componente _Pai_
Usado quando você deseja que o componente _Filho_ utilize métodos e/ou atributos do componente _Pai_. Este procedimento é um dos mais simples, bastando apenas passar os métodos e/ou atributos do componente _Pai_ para o componente _Filho_.

#### Componente _Pai_
Primeiramente, vamos instanciar um componente _Parent_ com apenas um atributo _name_ a fim de uma demonstração genérica:
~~~javascript
interface Props {
    readonly name: string;
}

const Parent: React.FC<Props> = ({ name }) => {
~~~
O componente _Parent_ possui o método _handleLabel_ que faz alteração do Label presente dentro do componente _Parent_.
~~~javascript
const handleLabel = (novoLabel: string) => {
	setWord(novoLabel);
}
~~~
Dentro deste componente _Parent_, vamos incluir o componente _Child_ com a função _handleLabel_ como parâmetro, além de um _name_ para ilustração.
~~~javascript
<Child name={"Simba"} changeLabel={handleLabel}></Child>
~~~
O resultado da implementação fica da seguinte maneira:
~~~javascript
interface Props {
    readonly name: string;
}

const Parent: React.FC<Props> = ({ name }) => {

    const [word, setWord] = useState<string>("you forgot who you are")

    const handleLabel = (novoLabel: string) => {
        setWord(novoLabel);
    }
    
    return (
        <Container color={"#FAF9F5"} colorName={"#5B709B"}>
            <p>{name}</p>
            <h6>says: {word}</h6>
            <Child name={"Simba"} changeLabel={handleLabel}></Child>
        </Container>
    );
};
~~~

#### Componente _Filho_
Já no componente _Filho_, vamos instanciá-lo com os mesmos atributos repassados no componente _Pai_:
~~~javascript
interface  Props {
	readonly  name: string;
	readonly  changeLabel: (arg0: string) =>  void;
}

const  Child: React.FC<Props> = ({ name, changeLabel }) => {
~~~
O componente _Filho_ possui o método _sendNewLabel()_ que envia o novo label descrito no componente _Filho_ para o componente _Pai_.
~~~javascript
const  sendNewLabel = () => {
	if (document.getElementById("novoLbl")) {
		const  novoLabel = (document.getElementById("novoLbl") as  HTMLInputElement).value;
		changeLabel(novoLabel);
	}
}
~~~

Por fim, o componente _Filho_ é constituído por um input onde será digitado o novo label e um botão que será o gatilho do evento.
~~~javascript
<input  type="text"  id="novoLbl"></input>
<button  onClick={sendNewLabel}>Send</button>
~~~

Juntando tudo:
~~~javascript
interface  Props {
	readonly  name: string;
	readonly  changeLabel: (arg0: string) =>  void;
}

const  Child: React.FC<Props> = ({ name, changeLabel }) => {

	const  sendNewLabel = () => {
		if (document.getElementById("novoLbl")) {
			const  novoLabel = (document.getElementById("novoLbl") as  HTMLInputElement).value;
			changeLabel(novoLabel);
		}
	}

	return (
		<Container  color={"#5B709B"}  colorName={"#FAF9F5"}>
			<p>{name}</p>
			<input  type="text"  id="novoLbl"></input>
			<button  onClick={sendNewLabel}>Send</button>
		</Container>
	);

};

export  default  Child;
~~~

O fluxo das ações descritas acima se comporta da seguinte forma:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zgvw7z4v00ztvyy2eyn1.gif)


### Componente _Pai_ utilizando métodos ou atributos de um Componente _Filho_
Usado quando você deseja que o componente _Pai_ utilize métodos e/ou atributos do componente _Filho_. Este procedimento pode parecer um pouco mais trabalhoso pois utiliza de mais alguns artifícios oferecidos pelo _React Hooks_.

#### Componente Pai
Novamente, vamos instanciar um componente _Parent_ com apenas um atributo _name_ a fim de mais uma demonstração genérica:
~~~javascript
interface Props {
    readonly name: string;
}

const Parent: React.FC<Props> = ({ name }) => {
~~~

Nesse caso, usaremos o _useRef_ que nada mais é que uma referência passada de um componente para outro. E vamos instanciar tal referência criando uma interface de objeto genérica que possui dois métodos para ilustração.
~~~javascript
interface  RefObject {
	alertLeave: () =>  void;
	alertBack: () =>  void;
}
~~~
~~~javascript
const  myRef = useRef<RefObject>(null)

const  handleLeave = () => {
	if (myRef.current) {
		myRef.current.alertLeave();
	}
}

const  handleBack = () => {
	if (myRef.current) {
		myRef.current.alertBack();
	}
}
~~~

Por fim, o componente _Parent_ é constituído por um nome e um componente _Child_, onde passaremos como parâmetros a referência instanciada anteriormente e um _name_ para ilustração.

~~~javascript
<Child  name={"Nemo"}  ref={myRef}></Child>
~~~

O resultado da implementação ocorre a seguir:
~~~javascript
interface  RefObject {
	alertLeave: () =>  void;
	alertBack: () =>  void;
}

interface Props {
    readonly name: string;
}

const Parent: React.FC<Props> = ({ name }) => {
	const  myRef = useRef<RefObject>(null)

	const  handleLeave = () => {
		if (myRef.current) {
			myRef.current.alertLeave();
		}
	}

	const  handleBack = () => {
		if (myRef.current) {
			myRef.current.alertBack();
		}
	}
	
	return (
		<Container  color={"#296766"}  colorName={"#F1B656"}  onMouseLeave={handleLeave}  onMouseOver={handleBack}>
			<p>{name}</p>
			<h6>is he looking for Nemo?</h6>
			<Child  name={"Nemo"}  ref={myRef}></Child>
		</Container>
	);
};
~~~

#### Componente Filho
Desta vez, no componente _Filho_, instanciaremos usando o método _forwardRef_ para que a referência passada pelo componente _Parent_ perca sua nulidade.
O componete _Child_ possui como parâmetros um _name_ para ilustração e a referência passada pelo componente _Parent_. Além disso, a referência passada pelo componente _Parent_ também obedece o formato o qual foi instanciada no componente _Parent_.

~~~javascript
interface  Props {
	readonly  name: string;
	ref: Ref<RefObject>
}

interface  RefObject {
	alertLeave: () =>  void;
	alertBack: () =>  void;
}

const  Child = forwardRef((props: Props, ref: Ref<RefObject>) => {
~~~

Dentro do componente _Child_ implementaremos os métodos assinalados na interface _RefObject_.
~~~javascript
const [him, setHim] = useState<string>("")

const  alertLeave = () => {
	setHim("is looking for you");
}

const  alertBack = () => {
	setHim("is back");
}
~~~

Depois de instanciados e implementados, vamos referenciar tais métodos à referência passada originalmente pelo componente _Parent_ usando o _useImperativeHandle_:

~~~javascript
useImperativeHandle(ref, () => ({ alertLeave, alertBack }));
~~~

Juntando esse negócio todo e quase terminando, temos:
~~~javascript
interface  Props {
	readonly  name: string;
	ref: Ref<RefObject>
}

interface  RefObject {
	alertLeave: () =>  void;
	alertBack: () =>  void;
}

const  Child = forwardRef((props: Props, ref: Ref<RefObject>) => {
	const [him, setHim] = useState<string>("")

	const  alertLeave = () => {
		setHim("is looking for you");
	}

	const  alertBack = () => {
		setHim("is back");
	}
	
	useImperativeHandle(ref, () => ({ alertLeave, alertBack }));

	return (
		<Container  color={"#F1B656"}  colorName={"#296766"}>
			<p>{props.name}</p>
			<span><h6>Marlin {him}</h6></span>
		</Container>
	);
});

export  default  Child;
~~~

O fluxo das ações descritas acima se comporta da seguinte forma:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/akyhz91vcjvf47zcqkkz.gif)

Espero que o surto já tenha sido dissolvido ao fim dessa leitura :).

O código completo das demonstrações está disponível, ó: [https://github.com/matheusarauj/demoTypescriptHooks](https://github.com/matheusarauj/demoTypescriptHooks)