DEV Community

Matheus Araújo
Matheus Araújo

Posted on

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

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:

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.

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.

<Child name={"Simba"} changeLabel={handleLabel}></Child>

O resultado da implementação fica da seguinte maneira:

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:

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.

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.

<input  type="text"  id="novoLbl"></input>
<button  onClick={sendNewLabel}>Send</button>

Juntando tudo:

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

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:

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.

interface  RefObject {
    alertLeave: () =>  void;
    alertBack: () =>  void;
}
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.

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

O resultado da implementação ocorre a seguir:

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.

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.

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:

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

Juntando esse negócio todo e quase terminando, temos:

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

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

Top comments (0)