Skip to content

Latest commit

 

History

History
4830 lines (3703 loc) · 125 KB

README.md

File metadata and controls

4830 lines (3703 loc) · 125 KB

React

React é um framework single page application(aplicação de única página).

Criando o primeiro projeto React

  • Dentro da pasta do projeto, vamos rodar o comando:
npx create-react-app fundamentos-react
                     [nome-aplicação]

npx: Comando que vai se encarregar de baixar uma dependência que vamos usar apenas de forma temporária. Ele baixa essa dependência, executa o que tem que ser executado e depois exclui ela. Desse modo, não instalamos as dependências de forma global, evitando ocupar espaço na máquina.

create-react-app: Dependência para criar um projeto.

  • Ou, podemos rodar o comando, para instalar o comando create-reatc-app globalmente na nossa máquina:
sudo npm install -g create-react-app
  • E em seguida podemos usá-lo:
create-react-app fundamentos-react
                 [nome-aplicação]
  • Ao finalizar a criação ele informa no terminal os próximos passos. Primeiro, entrar na pasta da aplicação:
cd fundamentos-react
   [nome-pasta-aplicação]
  • Segundo, rodar o comando para iniciar o projeto:
npm start

Dentro do vscode, podemos notar que foram criados os seguintes diretórios no projeto:

node_modules
public
src

Vamos excluir todos os arquivos da pasta src. Com isso, nossa aplicação vai parar, pois ele está procurando um arquivo index.js e esse arquivo não existe mais. Vamos recriar o arquivo index.js vazio.

  • Agora, vamos parar a aplicação
ctrl + c
  • E rodar novamente, para que a página seja recarregada:
npm start

Exibindo uma string na tela

Precisamos que o index.js interaja com uma biblioteca chamada React DOM - Document Object Model(Modelo de Objeto de Documentos) é que exatamente a extrutura da nossa página.

  • Para isso vamos importar o React DOM no nosso arquivo index.js:
import ReactDOM from "react-dom";
      [nome_variavel] [nome_módulo]

A partir do React DOM vamos renderizar "algo" na tela. Então vamos chamá-lo junto ao método render. Esse método recebe dois parâmetros, o primeiro é o que queremos renderizar na tela e o segundo é o elemento o qual queremos inserir esse conteúdo.

const elDivBody = document.getElementById("root");
ReactDOM.render("Olá React!", elDivBody);

Conhecendo o JSX

É uma extensão de sintaxe para JavaScript. É recomendado usar JSX com o React para descrever como a UI deveria parecer. JSX pode lembrar uma linguagem de template, mas que vem com todo o poder do JavaScript. Nada mais é que código JavaScript com "cara" de HTML.

  • Para utilizarmos o JSX é necessário realizar o import do React no arquivo:
import React from 'react'
  • Podemos agora, alterar o código da exibição da string, e envolvelá dinamicamente com uma div, usando código JavaScript com o JSX:
const elDivBody = document.getElementById("root");
ReactDOM.render(<div>Olá React!</div>, elDivBody);

Outras possibilidades:

const elDivBody = document.getElementById("root");
const tag = <strong>Olá React!</strong>

ReactDOM.render(
  <div>
    { tag }
  </div>,
  elDivBody
);

Carregando CSS

Na pasta src, vamos criar o arquivo index.css.

  • Vamos importar(import relative) esse arquivo dentro de index.js:
import "./index.css"; // não atribuimos um nome a esse import, pois não vamos acessar nada(nenhum componente) desse arquivo, só queremos que nossa aplicação seja capaz de carregá-lo

Primeiro componente

  • Vamos criar uma pasta dentro de src, chamada components. Dentro de components vamos criar uma pasta basics, para deixá-los bem organizados.

  • Dentro da pasta basics vamos criar o primeiro arquivo, chamado First.js. Por padrão, todos os componentes são criados com a primeira letra maiúscula.

  • No componente First.js, vamos criar uma função que não recebe nenhum parâmetro de entrada e retorna uma string:

function Primeiro() {
  return "Primeiro Componente";
}
  • Precisamos que essa função fique disponível fora desse arquivo, para isso vamos exportar ela:
export default function Primeiro() {
  return "Primeiro Componente";
}
  • No arquivo index.js vamos importar essa Função:
import Primeiro from "./components/basics/First";
  • E referenciar esse componente para que ele seja renderizado na tela:
const elDivBody = document.getElementById("root");

ReactDOM.render(
  <div>
    <Primeiro/> // renderizando componente
  </div>,
  elDivBody
);
  • Usando JSX dentro do componete. Primeiro, é necessário que seja feita a importação do React dentro do componente para o JSX seja interpretado:
import React from "react";

export default function Primeiro() {
  return (
    <div>
      <h2>Primeiro componente com JSX</h2>
    </div>
  );
}

Esses componentes são os chamados Componentes Funcionais, que são componentes baseados em função. Também existe os componentes baseados em classe.

Componente com propriedade

  • Dentro da pasta basics vamos criar um componente chamado WithParams(com parâmetros) com a extensão jsx. Não há nenhuma diferença em ser .js ou .jsx para o React, serve apenas para a IDE ajudar a completar o código jsx.

  • Vamos mudar a extensão do componente First para .jsx também, para padronizar.

  • No componente WithParams vamos criar uma função com alguns elementos(mas antes vamos importar o React para evitar erros):

import React from "react";

export default function ComParametros() {
  return (
    <div>
      <h2>titulo</h2>
      <h3>subtitulo</h3>
    </div>
  );
}

Nota-se que o retorno dessa função/componente espera receber o titulo e o subtitulo como parâmetro.

  • Para isso, nossa função vai receber esses parâmetros, que por padrão no React são chamados de props:
import React from "react";

export default function ComParametros(props) {
  return (
    <div>
      <h2>{ props.titulo }</h2>
      <h3>{ props.subtitulo }</h3> // chaves "{}" para interpretar o código, não somente renderizar
    </div>
  );
}

Obs.: Props são apenas leitura. Não conseguimos alterar uma propriedade. Se for necessário algum tratamento antes da renderização, podemos colocar em uma constante.

  • E no arquivo index.js vamos importar, renderizar esse componente e passar as props:
import ComParametros from "./components/basics/WithParams";

const elDivBody = document.getElementById("root");

ReactDOM.render(
  <div>
    <ComParametros
      titulo="Curso React"
      subtitulo="Aula Fundamentos do React"/>
  </div>,
  elDivBody
);

Se dermos o console.log em props, podemos notar que estamos recebemos um objeto, portando para acessar a propriedade é necessário utilizar props.[nome_propriedade]:

console.log(props);

// Retorno =>
{titulo: 'Curso React', subtitulo: 'Aula Fundamentos do React'}

React Fragment

Quando não envolvemos o código JSX com algum elemento é retornado o segunte erro: Adjacent JSX elements must be wrapped in an enclosing tag(Elementos JSX adjacentes devem ser agrupados em uma tag delimitadora).

  • Como exemplo dentro de components/basics vamos criar um componente chamado Fragment:
import React from "react";

export default function Fragmento(props) {
  return (
    <div>
      <h2>Fragmento</h2>
      <p>Cuidado com esse erro!</p>
    </div>
  );
}

Podemos notar que o conteúdo JSX está sendo delimitado por um elemento do tipo div.

  • Caso não queiramos que esse elemento seja colocado dentro de div, podemos usar o React Fragment:
import React from "react";

export default function Fragmento(props) {
  return (
    <React.Fragment>
      <h2>Fragmento</h2>
      <p>Cuidado com esse erro!</p>
    </React.Fragment>
  );
}
  • Ou sua sintaxe reduzida:
import React from "react";

export default function Fragmento(props) {
  return (
    <>
      <h2>Fragmento</h2>
      <p>Cuidado com esse erro!</p>
    </>
  );
}

Com a sintaxe reduzida, só não é possível utilizar atributos extras.

Componente App

  • Na pasta src vamos criar um componente chamado App que vai representar a nossa aplicação.

  • Vamos usar esse componente para enxugar os códigos JSX de index.js(vamos trazer junto todos os imports):

import React from "react";

import Primeiro from "./components/basics/First";
import ComParametros from "./components/basics/WithParams";
import Fragmento from "./components/basics/Fragment";

export default function App(props) {
  return (
    <div>
      <Primeiro/>
      <ComParametros
        titulo="Situação do aluno:"
        aluno="João"
        nota={9.2}
      />
      <ComParametros
        titulo="Situação da aluna:"
        aluno="Maria"
        nota={5.8}
      />
      <Fragmento/>
    </div>
  );
}
  • No arquivo index.js, vamos importar e renderizar App:
import App from "./App";

const elDivBody = document.getElementById("root");

ReactDOM.render(
  <App/>,
  elDivBody
);

A partir de agora não vamos mais mexer em index.js, vamos utilizar dessa referência a App.jsx.

  • Forma mais reduzida de declarar funções é usando arrow function:
export default (props) => {
  return (
    <div>
      <Primeiro/>
      <ComParametros
        titulo="Situação do aluno:"
        aluno="João"
        nota={9.2}
      />
      <ComParametros
        titulo="Situação da aluna:"
        aluno="Maria"
        nota={5.8}
      />
      <Fragmento/>
    </div>
  );
}
  • Quando a função tem apenas um parâmetro, podemos tirar os parênteses:
export default props => {
  return (
    <div>
      <Primeiro/>
      <ComParametros
        titulo="Situação do aluno:"
        aluno="João"
        nota={9.2}
      />
      <ComParametros
        titulo="Situação da aluna:"
        aluno="Maria"
        nota={5.8}
      />
      <Fragmento/>
    </div>
  );
}
  • Quando a função não tem nenhum parâmetro, podemos usar apenas os parênteses vazio ():
export default () => {
  return (
    <div>
      <Primeiro/>
      <ComParametros
        titulo="Situação do aluno:"
        aluno="João"
        nota={9.2}
      />
      <ComParametros
        titulo="Situação da aluna:"
        aluno="Maria"
        nota={5.8}
      />
      <Fragmento/>
    </div>
  );
}
  • Quando a função tem apenas um parâmetro e não vamos usá-la, podemos usar apenas o _:
export default _ => {
  return (
    <div>
      <Primeiro/>
      <ComParametros
        titulo="Situação do aluno:"
        aluno="João"
        nota={9.2}
      />
      <ComParametros
        titulo="Situação da aluna:"
        aluno="Maria"
        nota={5.8}
      />
      <Fragmento/>
    </div>
  );
}
  • Podemos tirar o corpo da arrow function e de forma implicita tudo que está dentro da função vai ser retornado, portanto, não é necessário utilizar a palavra reservada return:
export default () => (
  <div>
    <Primeiro/>
    <ComParametros
      titulo="Situação do aluno:"
      aluno="João"
      nota={9.2}
    />
    <ComParametros
      titulo="Situação da aluna:"
      aluno="Maria"
      nota={5.8}
    />
    <Fragmento/>
  </div>
);

Class com JSX

Diferente do html que para atribuir uma classe css para um elemento especificamos class="", no jsx como "class" é uma palavra reservada do JavaScript, usa-se className="".

export default function Card(props) {
  return (
    <div className="Card">
      <div>{props.titulo}</div>
      <div>Conteúdo</div>
    </div>
  );
}

Componente com filho

Quando quisermos referênciar um componente dentro de outro, vamos usar props.children, para resgatar os filhos que são passados dentro de determinado componente(componentes passados dentro do corpo de outro componente).

  • No código abaixo o componente Card tem o componente filho Primeiro dentro dele:
export default function App(props) {
  return (
    <div className="App">
      <h1>Fundamentos React</h1>

      <Card titulo="Exemplo de Card">
        <Primeiro/>
      </Card>
  )

Importante resaltar que quando estamos trabalhando com componentes filhos, devemos fechar a tag do componente pai da forma completa.

  • E para recuperar os filhos de dentro de Card e renderizar seus conteúdos, vamos utilizar o props.children dentro do arquivo do componente Card (Card.jsx):
import "./Card.css";
import React from "react";

export default function Card(props) {
  return (
    <div className="Card">
      <div className="Title">{props.titulo}</div>
      <div className="Content">{props.children}</div>
    </div>
  );
}
  • Agora temos um componente Familia que recebe em seu escopo o Componente MembroFamilia:
import React from "react";
import MembroFamilia from "./FamilyMember";

export default function Familia(props) {
  
  return (
    <div>
      <MembroFamilia nome="Nathallye" sobrenome="Bacelar" />
      <MembroFamilia nome="Paulo" sobrenome="Bacelar" />
      <MembroFamilia nome="Maria" sobrenome="Bacelar" />
    </div>
  );
}
import React from "react";

export default function MembroFamilia(props) {
  
  return (
    <div>
      {props.nome} <strong>{props.sobrenome}</strong>
    </div>
  );
}
  • Nota-se que estamos passando o sobrenome para todos os elementos, mas isso não é necessário, pois todos tem o mesmo sobrenome por serem parte da mesma familia. Uma saída seria passar esse parâmetro do componente Pai, ou seja, na renderização do componente Família(em App.jsx).
// [...]
import Familia from "./components/basics/Family";

  // [...]
    <div>
      // [...]

      <Card titulo="#05 - Componente com Filhos" color="#86BAAB">
        <Familia sobrenome="Ferraz"/>
      </Card>

    </div>
  • Mas isso não vai funcionar ainda, pois o parâmetro do componente pai não é passado automáticamente para o componente filho. A forma mais simples de fazer funcionar é resgatar essa propriede que está sendo enviada pelo componente pai(props.sobrenome) no componente filho:
import React from "react";
import MembroFamilia from "./FamilyMember";

export default function Familia(props) {
  
  return (
    <div>
      <MembroFamilia nome="Nathallye" sobrenome={props.sobrenome} />
      <MembroFamilia nome="Paulo" sobrenome={props.sobrenome} />
      <MembroFamilia nome="Maria" sobrenome={props.sobrenome} />
    </div>
  );
}
  • Outra forma que temos como fazer isso é usando o operador spred para pegar todas as propriedade passadas para esse componente filho:
import React from "react";
import MembroFamilia from "./FamilyMember";

export default function Familia(props) {
  
  return (
    <div>
      <MembroFamilia nome="Nathallye" {...props} />
      <MembroFamilia nome="Paulo" {...props} />
      <MembroFamilia nome="Maria" {...props} />
    </div>
  );
}

E se quisermos passar os componentes filhos de forma literal na renderização do componente Pai?

  • Nesse caso dentro do nosso arquivo principal, App.jsx:
// [...]
import Familia from "./components/basics/Family";
import MembroFamilia from "./components/basics/FamilyMember"

  // [...]
  
    <div>
      // [...]

      <Card titulo="#05 - Componente com Filhos" color="#86BAAB">
        <Familia sobrenome="Ferraz">
          <MembroFamilia nome="Nathallye" sobrenome={props.sobrenome} />
        </Familia>
      </Card>

    </div>
  • E apenas referenciar os filhos dentro do arquivo do Componente pai(Familia.jsx) a partir de props.children:
import React from "react";

export default function Familia(props) {
  
  return (
    <div>
      {props.children}
    </div>
  );
}

Quando salvarmos, isso vai gerar um problema, pois não temos props em nenhum local. Não estamos recebendo props e não temos como pegar as propriedades que estão sendo passadas no componente pai(que nesse caso é sobrenome) para o componente filho (sobrenome={props.sobrenome}), não é assim que funciona.

  • Para o componente filho receber as props do componente pai, é necessário que dentro do arquivo do componente Pai seja implementando algumas alterações. Sabemos que dentro de chaves "{}" podemos inserir código javascript e acessar seus métodos, assim também podemos acessar os métodos do react("React."). Existe um método chamado choneElement, ele vai clonar um elemento("React.cloneElement()"), e dentro dele vamos passar o elemento props.children(que nada mais é que as propriedades do elemento filho).
import React from "react";

export default function Familia(props) {
  return (
    <div>
      {React.cloneElement(props.children)}
    </div>
  );
}
  • Para o cloneElement além de passarmos o elemento, podemoos passar como segundo parâmetro as props(para clonar as propriedade):
import React from "react";
//import React, { cloneElement } from "react"; quando usamos o import assim não precisamos expecificar o React antes do método(React.cloneElement)

export default function Familia(props) {
  return (
    <div>
      {React.cloneElement(props.children, props)} 
    </div>
  );
}

Mas isso vale somente quando temos apenas um componente filho, quando há mais de um ocorre um erro.

  • Podemos contornar esse erro, usando o map:
import React, { cloneElement } from "react";

export default function Familia(props) {

  return (
    <div>
      {
        React.Children.map(props.children, (child) => { // recebemos a lista de todos os filhos epara cada filho vamos chamar a função que recebe cada um dos filhos
          return cloneElement(child, props); // e essa função retorna para cada item o clone do elemetro filho(nomes) e passa as propriedades que foi enviada pelo elemento pai(sobrenome) para o elemento clonado
        })
      }
    </div>
  );
}
  • Como o props.childer e um objeto que contem todos as propriedades que são passadas aos filhos, podemos usar o método map diretamente nele, simplificando o código:
import React, { cloneElement } from "react";

export default function Familia(props) {
  
  return (
    <div>
      {
        props.children.map((child) => { 
          return cloneElement(child, props); 
        })
      }
    </div>
  );
}

Renderização condicional

  • Vamos criar um componente para renderizar um número e mostrar se ele é ímpar ou par:
import React from "react";

export default function ParOuImpar(props) {
  return (
    <div>
      <span>Par</span>
      <span>Ímpar</span>
    </div>
  )
}
  • E dentro do arquivo principal de renderização(App.jsx) vamos renderizar esse componente e passar um número como propriedade:
// [...]
import ParOuImpar from "./components/conditional/EvenOrOdd";

  // [...]
  
    <div>
      // [...]

      <Card titulo="#08 - Renderização condicional" color="#006400">
          <ParOuImpar numero={20}/>
      </Card>

    </div>

Até agora, não há nenhuma lógica que faça esse número ser renderizado como ímpar ou par.

  • Para isso, vamos criar uma constante para saber se o número é par:
import React from "react";

export default function ParOuImpar(props) {
  const isPar = props.numero % 2 === 0;

  return (
    <div>
      <span>Par</span>
      <span>Ímpar</span>
    </div>
  )
}
  • E vamos usar essa variável dentro de um par de chaves para ser interpretado como código javascript e dentro vamos criar uma operação ternária((?)se caso for par vai ser renderizado Par (:)senão vai ser renderizado Ímpar):
import React from "react";

export default function ParOuImpar(props) {
  const isPar = props.numero % 2 === 0;

  return (
    <div>
      { isPar ? <span>Par</span> : <span>Ímpar</span>}
    </div>
  )
}
  • Também é possível criarmos um componente e a partir desse componente conseguimos renderizar um trecho de jsx ou não. Para exemplificar, vamos criar um componente chamado If.

  • Esse componente funcional vai receber como propriedade um expressão(verdadeira ou falsa), caso seja verdadeira vai retornar o seu corpo, ou seja, os filhos desse componente:

export default function If(props) {
  if(props.test) {
    return props.children
  } else {
    return false
  }
}
  • Vamos testar criando um outro componente UsuarioInfo. Dentro desse componente funcional, vamos retornar uma div com as informações do usuário:
import React from "react";

export default function UsuarioInfo(props) {
  return (
    <div>
      Seja bem vindo <strong>{ props.usuario.nome }</strong>!
    </div>
  );
}

Vamos passar o usuário, e uma vez passado o usuário vamos mostrar de forma condicional. Caso não tenha um usuário, vamos retornar um outro trecho de código.

  • No arquivo de renderização principal(App.jsx), vamos importar esse componente e inserir esse componente dentro de um card, e passar como propriedade um objeto que representa usuario:
// [...]
import UsuarioInfo from "./components/conditional/UserInfo";

  // [...]
  
    <div>
      // [...]

      <Card titulo="#08 - Renderização condicional" color="#006400">
          <ParOuImpar numero={20}/>
          <UsuarioInfo usuario={{ nome: 'Fernando' }}/> {/*o primeiro par de chaves"{}" é para ter um trecho que vai ser interpretado com javascript; se queremos criar um objeto de forma literal temos que colocar um outro par de chaves, dentro.*/}
      </Card>

    </div>
  • Para evitar erros, podemos colocar props.usuario dentro de uma constante e passar um valor padrão caso não seja passado nada:
import React from "react";

export default function UsuarioInfo(props) {
  const usuario = props.usuario || {}

  return (
    <div>
      Seja bem vindo <strong>{ usuario.nome }</strong>!
    </div>
  );
}
  • Agora, podemos importar o componente funcional If que vai receber como propriedade um expressão. E vamos inserir esse componente If e colocar a mesagem para o usuário dentro do corpo dele. Dentro das chaves, vamos inserir a expressão que vai verificar se o usuário está setado && se o nome do usuário é válido(diferente de null/vazio):
import React from "react";
import If from "./If";

export default function UsuarioInfo(props) {
  const usuario = props.usuario || {}

  return (
    <div>
      <If test={usuario && usuario.nome}>
        Seja bem vindo <strong>{ usuario.nome }</strong>!
      </If>
    </div>
  );
}
  • Podemos também criar um outro trecho que vai validar o contrário e retornar outro trecho de código... se não tiver usuário(!usuario) OU(||) o nome do usuário não for válido(!usuario.nome):
import React from "react";
import If from "./If";

export default function UsuarioInfo(props) {
  const usuario = props.usuario || {}

  return (
    <div>
      <If test={usuario && usuario.nome}>
        Seja bem vindo <strong>{ usuario.nome }</strong>!
      </If>
      <If test={!usuario || !usuario.nome}>
        Seja bem vindo <strong>Fulano</strong>!
      </If>
    </div>
  );
}

Comunição Direta

A nossa aplicação em React é uma árvore de componentes. Podemos quebrar nossa aplicação em multiplos componentes sempre visando o reuso a organização. E dentro dessa árvore de componentes é muito comum que tenhamos uma comunição direta e indireta.

Para exemplificarmos melhor dentro da pasta componentes vamos criar uma pasta chamada comunicacao e dentro dela vamos criar os componentes DiretaPai e DiretaFilho.

  • No componente DiretaFilho queremos receber um texto dentro de props(porps.texto), um número(props.numero) e um valor booleano(props.bool). Resumindo, vamos esperar receber esses três valores a partir do componente pai:
import React from "react";

export default function DiretaFilho(props) {
  return (
    <div>
      <div>{props.texto}</div>
      <div>{props.numero}</div>
      <div>{props.bool}</div>
    </div>
  );
}
  • Como funciona a comunicação de um componente pai, para um componente filho? É passarmos via props(propriedades) aquilo que queremos passar do pai para o filho. Ate porquê há uma relação direta, pois dentro do pai normalmente temos uma referência para o componente filho(import do componente filho, de tal forma que conseguimos passar as props para o filho):
import React from "react";
import DiretaFilho from "./DirectChild";

export default function DiretaFilho(props) {
  return (
    <div>
      <DiretaFilho text="Filho 1" numero={20} bool={true}/>
    </div>
  );
}
  • De tal forma que podemos voltar no DiretaFilho e tratar esse valor booleano(bool) com operadores ternários. Se for true renderiza "verdadeiro" senão renderiza "falso":
import React from "react";

export default function DiretaFilho(props) {
  return (
    <div>
      <div>Texto: {props.texto}</div>
      <div>Número: {props.numero}</div>
      <div>Booleano: {props.bool ? "Verdadeiro" : "Falso"}</div>
    </div>
  );
}
  • Dessa forma, podemos inserir esse componente no arquivo de renderização principal(App.jsx):
// [...]
import DiretaPai from "./components/communication/DirectFather"

  // [...]
  
    <div>
      // [...]

      <Card titulo="#09 - Comunicação Direta" color="#C2A886">
        <DiretaPai/>
      </Card>

    </div>

Resumindo: Isso é uma comunicação direta, temos uma referência para um componente filho, e conseguimos no momento que criamos a instância do componente, passar as propriedades do pai para o filho. A questão da comunicação direta... comunicação sugere troca de dados, e a troca de dados do componente pai para o componente filho é passarmos via propriedades(props) o que queremos passar do pai para o filho.

Comunicação Indireta

Quando precisamos passar informações do componente filho para o componente pai. O componente filho não tem uma referência direta com o componente pai, então não temo como via propriedades(porps) intânciar um componente pai(senão o pai passaria a ser filho e o filho passaria a ser pai).

Para exemplificarmos melhor dentro da comunicacao vamos criar os componentes IndiretaPai e IndiretaFilho.

  • Vamos inserir dentro dos Componetes a extrutura básica de um Componente funcional. Lembrando que o componente IndiretaPai leva a referência do componente filho:
import React from "react";
import IndiretaFilho from "./IndirectChild";

export default function IndiretaPai(props) {
  return (
    <div>
      Pai
      <IndiretaFilho/>
    </div>
  );
}
import React from "react";

export default function IndiretaFilho(props) {
  return (
    <div>
      Filho
    </div>
  );
}
  • E vamos importar esse componente no nosso arquivo principal(App.js):
// [...]
import IndiretaPai from "./components/communication/IndirectFather";

  // [...]
  
    <div>
      // [...]

      <Card titulo="#10 - Comunicação Indireta" color="#4F4537">
        <IndiretaPai/>
      </Card>

    </div>
  • Vamos supor que no componente filho temos um botão, e quando clicarmos nele as informações vão ser enviadas para o componente pai:
import React from "react";

export default function IndiretaFilho(props) {
  return (
    <div>
      <div>Filho</div>
      <button>Fornecer Informações</button>
    </div>
  );
}

Mas de fato como é feito a comunicação do sentido do filho enviar dados para o pai, já que o filho não tem nenhum tipo de comunicação direta com o pai, diferente do pai que tem o componente filho(dentro dele). Fazemos isso com uma função via props, ou seja, passamos uma função do pai para o filho e quando o filho chamar essa função(acontece um evento), temos como passar informações para o pai.

  • Vamos criar uma função com o nome fornecerInformacoes e ela vai receber 3 atributos(texto, numero e um booleano) e vai mostrar no console esses atributos:
import React from "react";
import IndiretaFilho from "./IndirectChild";

export default function IndiretaPai(props) {
  function fornecerInformacoes(texto, numero, bool) {
    console.log(texto, numero, bool)
  }

  return (
    <div>
      <div>Pai</div>
      <IndiretaFilho/>
    </div>
  );
}
  • Como vamos conseguir passar essa função para o filho? Via props. Vamos ter a propriedade quandoClicar vai ser chamada a função fornecerInformações:
import React from "react";
import IndiretaFilho from "./IndirectChild";

export default function IndiretaPai(props) {
  function fornecerInformacoes(texto, numero, bool) {
    console.log(texto, numero, bool)
  }

  return (
    <div>
      <div>Pai</div>
      <IndiretaFilho quandoClicar={fornecerInformacoes}/>
    </div>
  );
}
  • O que significa que dentro do filho vamos ter dentro de props esse atributo quandoClicar e podemos chamá-la através de props.quandoClicar. NO botão vamos rastrear o evento de click com o método onClick e dentro vamos receber uma função anônima que recebe um evento(e) e dentro dessa função anônima vamos chamar a função quandoClicar via props e passar como parâmetro dessa função os dados para o componente pai:
import React from "react";

export default function IndiretaFilho(props) {
  return (
    <div>
      <div>Filho</div>
      <button>Fornecer Informações</button>
    </div>
  );
}

Podemos passar essas informações a partir do método que foi passado como propriedade do pai para o filho, invocamos o método e conseguimos passar as informações para o pai. Essa é a comunicação indireta, o pai passa para o filho uma função callback(será chamada de volta em algum momento) e na chamada da função podemos retornar as propriedades para o pai.

Componente com Estado

A partir do React 16.8 é possível ter estado em componentes funcionais.

  • Temos aqui o caso anterior que o componente pai passou uma função via props para o componente filho, e o componente filho a partir de um evento(nesse caso foi o cick do botão) chama a função e passou informações para o componente pai. E agora, como conseguimos mudar esses valores no componente pai, ou seja, não basta criarmos variáveis e mandarmos esses valores que foram passadas para o componente pai, como no exemplo abaixo, temos que manter estado:
import React from "react";
import IndiretaFilho from "./IndirectChild";

export default function IndiretaPai(props) {
  let nome = '?';
  let idade = 0;
  let nerd = false;

  function fornecerInformacoes(texto, numero, bool) {
    nome = texto;
    idade = numero;
    nerd = bool;
    
    console.log(texto, numero, bool)
  }

  return (
    <div>
      <div>
        <div>Nome: {nome}</div>
        <div>Idade: {idade}</div>
        <div>Nerd: {nerd ? "Verdadeiro" : "Falso"}</div>
      </div>

      <IndiretaFilho quandoClicar={fornecerInformacoes}/>
    </div>
  );
}
  • Para isso vamos importar o hook useState do React:
import React, { useState } from "react";
  • A partir do useState conseguimos criar estado dentro do nosso componente. Então ao invés de criarmos uma variável simplesmente colocando o valor dela:
  let nome = '?';
  • Vamos chamar o useState e passar o valor inicial:
  let nome = useState('?');
  • Só que essa função vai retornar um array com duas possições, a primeira possição vai ser o valor e a segunda posição vai ser uma função que vai ser usada para alterar esse valor. Como bem sabemos podemos usar atribuição via desestruturação (destructuring assignment). Ex.: const [a, b] = [1, 2], fica assim, a=1 e b=2. Desse modo, vamos usar o destructuring para receber os dois valores do array que useState retorna:
  let [nome, setNome] = useState('?'); //nome: é o próprio valor que inicializamos a variável; setNome: função que vamos utilizar para alterar o nome.
  • E podemos aplicar a mesma atribuição para as demais variáveis:
import React, {useState} from "react";
import IndiretaFilho from "./IndirectChild";

export default function IndiretaPai(props) {
  
  let [nome, setNome] = useState('?');
  let [idade, setIdade] = useState(0);
  let [nerd, setNerd] = useState(false);

  //[...]
  );
}
  • E podemos alterar as variáveis que recebem os atributos da função fornecerInformacoes para a função set das mesmas, e passar como parâmetro o valor:
import React, {useState} from "react";
import IndiretaFilho from "./IndirectChild";

export default function IndiretaPai(props) {

  let [nome, setNome] = useState('?'); //nome: é o próprio valor que inicializamos a variável; setNome: função que vamos utilizar para alterar o nome.
  let [idade, setIdade] = useState(0);
  let [nerd, setNerd] = useState(false);

  function fornecerInformacoes(texto, numero, bool) {
    setNome(texto);
    setIdade(numero);
    setNerd(bool);
    
    console.log(texto, numero, bool)
  }

  return (
    <div>
      <div>
        <div>Nome: {nome}</div>
        <div>Idade: {idade}</div>
        <div>Nerd: {nerd ? "Verdadeiro" : "Falso"}</div>
      </div>

      <IndiretaFilho quandoClicar={fornecerInformacoes}/>
    </div>
  );
}

Nota-se que inicializamos os valores de nome, idade, nerd, pois não tinhamos as informações que foram passadas pelo componente filho. A partir do evento inicialização da função fornerInformacoes(evento de click do botão) recebemos os valores de texto, numero e bool e passamos como respectivos parâmetros das funções set de cada variável de estado.

Componente Controlado

Os elementos de formulário HTML funcionam de maneira um pouco diferente de outros elementos DOM no React, porque os elementos de formulário mantêm naturalmente algum estado interno. Por exemplo, este formulário em HTML puro aceita um único nome:

<form>
  <label>
    Nome:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Enviar" />
</form>

Esse formulário tem o comportamento padrão do HTML de navegar para uma nova página quando o usuário enviar o formulário. Se você quer esse comportamento no React, ele simplesmente funciona. Mas na maioria dos casos, é conveniente ter uma função JavaScript que manipula o envio de um formulário e tem acesso aos dados que o usuário digitou nos inputs. O modo padrão de fazer isso é com uma técnica chamada “componentes controlados” (controlled components).

Para entendermos melhor como funciona um componente controlado, dentro da pasta componentes vamos criar uma pasta chamada formulario e dentro dela vamos criar um componente chamado Input.jsx.

  • Vamos inserir dentro do Componete a extrutura básica de um Componente funcional:
import React from "react";

export default function Input(props) {
  
  return (
    <div>
      Input
    </div>
  );
}
  • Em seguida, vamos importar esse componente dentro do arquivo de renderização principal(App.jsx):
// [...]
import Input from "./components/form/Input";

  // [...]
  
    <div>
      // [...]

      <Card titulo="#11 - Componente Controlado" color="#FE3D56">
        <Input/>
      </Card>
    </div>
  • No componente Input vamos definir uma variável com estato. Vamos importar o useState do React e criar uma variável com o destructuring, sendo que a primeira posição vai receber o valor inicial e a segunda posição vai receber a função set para alterar a variável:
import React, { useState } from "react";

export default function Input(props) {
  
  const [valor, setValor] = useState('Inicial')

  return (
    <div>
      Input
    </div>
  );
}
  • Vamos definir um elemento input com o atributo value que recebe a variável valor:
import React, { useState } from "react";

export default function Input(props) {
  
  const [valor, setValor] = useState('Inicial')

  return (
    <div>
      <input value={valor} />
    </div>
  );
}
  • Podemos notar no navegador o componente Input com o valor inicial. Mas como é um componente controlado não conseguimos alterar o valor do input. O que ele chama de verdade absoluta são os dados, ou seja, o estado do componente não mudou, não foi chamada em nenhum momento a função setValor para mudar o dado. Resumindo, não conseguimos mudar o estado de um componente diretamente a partir da interface, primeiro temos que mudar o estado, para quando esse estado mudar aí sim conseguimos refletir essa mudança na interface gráfica. O caminho é unidirecional, o estado muda e altera a interface gráfica. A interface gráfica não altera o estado(isso acontece indiretamente a partir dos eventos).

  • Então, nesse caso, como conseguimos alterar o valor do input? Podemos mudar ele pegando o evento onChange, esse evento vai ser chamado toda vez que digitarmos:

import React, { useState } from "react";

export default function Input(props) {
  
  const [valor, setValor] = useState('Inicial')

  return (
    <div>
      <input value={valor} onChange={}/>
    </div>
  );
}
  • Podemos atribuir uma função a ele, e essa função vai receber como parâmetro o evento. Então, vamos criar uma função quandoMudar e ela vai receber um evento "e"(sempre que dispararmos um evento associado ao input ele vai chamar o console.log):
import React, { useState } from "react";

export default function Input(props) {
  
  const [valor, setValor] = useState('Inicial')

  function quandoMudar(e) {
    console.log(e);
  }

  return (
    <div>
      <input value={valor} onChange={quandoMudar}/>
    </div>
  );
}
  • Se quisermos acessar mais especificamente, o e.target.value vai mostrar o valor novo que foi gerado a partir do evento:
import React, { useState } from "react";

export default function Input(props) {
  
  const [valor, setValor] = useState('Inicial')

  function quandoMudar(e) {
    console.log(e.target.value); //target = alvo; value = valor.
  }

  return (
    <div>
      <input value={valor} onChange={quandoMudar}/>
    </div>
  );
}
  • Ele mostra o que está sendo digitado mas ainda não altera o estado. Para alterar o estado dentro da função quandoMudar vamos chamar a função setValor e ela vai receber como valor o que estamos recebemos como resposta do evento(e.target.value):
import React, { useState } from "react";

export default function Input(props) {
  
  const [valor, setValor] = useState('Inicial')

  function quandoMudar(e) {
    setValor(e.target.value);
    // console.log(e.target.value); //target = alvo; value = valor.
  }

  return (
    <div>
      <input value={valor} onChange={quandoMudar}/>
    </div>
  );
}

Aí sim o setValor vai ser modificado e vai refletir no valor e vai ser renderizado no input.

  • Podemos também ter componentes somente de leitura, desse modo, ele não vai precisar do onChange:
<input value={valor} readOnly />

Componente baseado em Classe

Classe, nada mais é que uma forma diferente de escreve função.

Para entendermos melhor como funciona um componente controlado, dentro da pasta componentes vamos criar uma pasta chamada contador e dentro dela vamos criar um componente chamado Contador.jsx.

  • Vamos inserir dentro do Componete a extrutura básica de um Componente baseado em Classe. Vale ressaltar que a função render é obrigatória vai renderizar algo na tela:
import React, { Component } from "react";

class Contador extends Component {
  
  render() {
    return (
      <div>
        <h2>Contador</h2>
      </div>
    );
  }
}

export default Contador;
  • E vamos inserir esse componente dentro do nosso arquivo App.jsx:
// [...]
import Contador from "./components/counter/Counter";

  // [...]
  
    <div>
      // [...]

      <Card titulo="#12 - Componente de Classe - Contador" color="#670513">
        <Contador/>
      </Card>
    </div>
  • E como definimos estado dentro de um componente baseado em classe? Podemos definir o state como propriedade e essa propriedade pode ser definida como um objeto(no caso da função também podemos ter um objeto, usando o useState e atribuir um objeto dentro dele). Aqui no caso temos um único estado que é o state acessando-o a partir da classe, e dentro dele vamos colocar todos os atributos necessários do nosso contador. Inicialmente, o state vai ter um numero e ele vai inicializar com 0:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: 0
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
      </div>
    );
  }
}

export default Contador;
  • Vamos passar para esse componente um valor inicial para o contador, então em App.jsx no componente Contador vamos passar um atributo númeroINicial:
// [...]
import Contador from "./components/counter/Counter";

  // [...]
  
    <div>
      // [...]

      <Card titulo="#12 - Componente de Classe - Contador" color="#670513">
        <Contador numeroInicial={10}/>
      </Card>
    </div>
  • E agora, como podemos pegar essa propriedade que foi passada para o nosso componente baseado em classe? Podemos acessar ele a partir de props, só que não diretamente props, vamos usar o this.props, ou seja, as propriedades que pertecem a instância dessa classe(this aponta para a instância/objeto atual):
<p>Valor Inicial: { this.props.numeroInicial }</p>
  • Utilizando dessa lógica, vamos inicializar o state com o valor passado via props:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
      </div>
    );
  }
}

export default Contador;
  • Próximo passo, vamos criar uma função que vai incrementar o valor do contador. O que vamos fazer dentro dessa função? Vamos alterar o valor de numero ou seja, alterar state. Assim como, no useState temos uma função que altera o estado, aqui dentro da classe também vamos ter uma função para alterar o estado, e o nome dela é setState. Então, temos que chamar essa função setState dentro da função incremento para conseguirmos alterar o valor. Dentro da função setState vamos passar um novo objeto para state(já que ele é um objeto), e nesse objeto vamos passar o atributo que queremos alterar:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial
  }

  inc() {
    this.setState({
      numero: this.state.numero + 1 // vamos pegar o estado atual e acrescentar + 1 
    })
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
        <p>{ this.state.numero }</p>
      </div>
    );
  }
}

export default Contador;
  • Vamos utilizar um botão e dentro dele o evento de onClick e quando ele for acionado vai chamar a função de incrementar. Para evitar um erro do this não apontar para a instância correta, vamos utilizar na função de incremento uma arrow function:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial
  }

  inc = () => {
    this.setState({
      numero: this.state.numero + 1 // vamos pegar o estado atual e acrescentar + 1 
    })
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
        {/* <p>Valor Inicial: { this.props.numeroInicial }</p> */}
        <h3>{ this.state.numero }</h3>
        <button onClick={this.inc}>+</button>
      </div>
    );
  }
}

export default Contador;
  • E vamos seguir a mesma lógica para criar uma função de decremento:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial 
  }

  inc = () => {
    this.setState({
      numero: this.state.numero + 1 // vamos pegar o estado atual e acrescentar + 1 
    });
  }

  dec = () => {
    this.setState({
      numero: this.state.numero - 1 // vamos pegar o estado atual e subtrair - 1 
    });
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
        <h3>{ this.state.numero }</h3>
        <button onClick={this.inc}>+</button>
        <button onClick={this.dec}>-</button>
      </div>
    );
  }
}

export default Contador;
  • Outra implementação importante a ser feita no nosso contato é a quantidade que queremos incrementar ou decrementar(além de somente 1), que nada mais que o passo. Como podemos fazer isso? Criando uma nova propriedade dentro de state chamada passo:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, // se o numero inicial não for fornecido vai ser 0
    passo: this.props.passoInicial || 5 // se o passo inicial não for fornecido vai igual a 5
  }

  inc = () => {
    this.setState({
      numero: this.state.numero + 1 // vamos pegar o estado atual e acrescentar + 1
    });
  }

  dec = () => {
    this.setState({
      numero: this.state.numero - 1 // vamos pegar o estado atual e subtrair - 1 
    });
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
        {/* <p>Valor Inicial: { this.props.numeroInicial }</p> */}
        <h3>{ this.state.numero }</h3>
        <button onClick={this.inc}>+</button>
        <button onClick={this.dec}>-</button>
      </div>
    );
  }
}

export default Contador;
  • E vamos usar esse atributo passo para incrementar e decrementar de acordo com o valor passado:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  inc = () => {
    this.setState({
      numero: this.state.numero + this.state.passo 
    });
  }

  dec = () => {
    this.setState({
      numero: this.state.numero - this.state.passo 
    });
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
        <h3>{ this.state.numero }</h3>
        <button onClick={this.dec}>-</button>
        <button onClick={this.inc}>+</button>
      </div>
    );
  }
}

export default Contador;
  • Vamos criar também uma outra parte do template para definirmos um input e apartir desse input vai ser feita a alteração do passo:
import React, { Component } from "react";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  inc = () => {
    this.setState({
      numero: this.state.numero + this.state.passo 
    });
  }

  dec = () => {
    this.setState({
      numero: this.state.numero - this.state.passo 
    });
  }

  setPasso = (e) => { 
    this.setState({
      passo: +e.target.value 
    })
  }

  render() {
    return (
      <div>
        <h2>Contador</h2>
        <h3>{ this.state.numero }</h3>
        <div>
          <label htmlFor="passoInput">Passo: </label>
          <input 
            type="number" 
            value={this.state.passo} 
            id="passoInput"
            onChange={this.setPasso} 
          />
        </div>
        <button onClick={this.dec}>-</button>
        <button onClick={this.inc}>+</button>
      </div>
    );
  }
}

export default Contador;

Agora vamos dicidir esse nosso componente em 3, para deixar o código mais organizado. Primeiro componente vai ser o Display(a parte que mostra a contagem do valor), o segundo vai ser o formulário do passo e o terceiro vai ser os botões. Mas vamos continuar mantendo o estado do componente dentro de Contador(componente pai).

  • Vamos iniciar criando o componente Display.jsx e ele será um componente funcional:
import React from "react";

export default function Display(props) {
  return (
    <div>
      
    </div>
  );
}
  • E vamos seguir essa mesma extrutura para criar os componentes PassoFomulário e Botoes.

  • Dentro do contador o display representa apenas o trecho de código que está dentro do h3, que nada mais é que a variável de estado(state) e como essa variável recebe um objeto com seus atributos, vamos acessar o numero dela:

<h3>{ this.state.numero }</h3>
  • Vamos remover esse trecho de código de Contador e colocar no componente Display. Mas como não estamos mais trabalhando com função baseada em classe, vamos receber essa propriedade numero através de props:
import React from "react";

export default function Display(props) {
  return (
    
    <h3>{ props.numeroDisplay }</h3>
  
  );
}
  • E vamos importar o componente Display dentro do seu componente pai Contador. E vamos inserir no local do display o componente Display e passar o número para o componente filho:
// [...]
import Display from "./Display";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  // [...]

  render() {
    return (
      <div className="Contador">
        <h2>Contador</h2>
        <Display numeroDisplay={this.state.numero}/> {/*O componente pai passa via this.state o numero por ser um componente baseado em classe, e o componente filho recebe via props*/}
        
        // [...]
      </div>
    );
  }
}

export default Contador;
  • Antes de fazer a parte do PassoForm que é mais complexa, vamos migrar os botões do componente Contador para o componente filho Botoes:
import React from "react";

export default function Botoes(props) {
  return (
    <div>
      <button onClick={props.decrementar}>-</button>
      <button onClick={props.incrementar}>+</button>
    </div>  
  );
}
  • Agora, no Contador vamos importar o seu filho Botoes e inserir a referência desse componente onde os botoes ficavam:
// [...]
import Botoes from "./Buttons";

class Contador extends Component {
  
  // [...]

  inc = () => {
    this.setState({
      numero: this.state.numero + this.state.passo 
    });
  }

  dec = () => {
    this.setState({
      numero: this.state.numero - this.state.passo 
    });
  }

  // [...]

  render() {
    return (
      <div className="Contador">
        // [...]
        
        <div>
          <label htmlFor="passoInput">Passo: </label>
          <input 
            type="number" 
            value={this.state.passo} 
            id="passoInput"
            onChange={this.setPasso} 
          />
        </div>
        
        <Botoes />
      </div>
    );
  }
}

export default Contador;
  • Só que não passamos nada como parâmetro, então quando clicarmos não vai acontecer nada. Então, vamos passar como prarâmetro as funções inc e dec:
// [...]
import Botoes from "./Buttons";

class Contador extends Component {
  
  // [...]

  inc = () => {
    this.setState({
      numero: this.state.numero + this.state.passo 
    });
  }

  dec = () => {
    this.setState({
      numero: this.state.numero - this.state.passo 
    });
  }

  // [...]

  render() {
    return (
      <div className="Contador">
        // [...]
        
        <div>
          <label htmlFor="passoInput">Passo: </label>
          <input 
            type="number" 
            value={this.state.passo} 
            id="passoInput"
            onChange={this.setPasso} 
          />
        </div>
        
        <Botoes decrementar={this.dec} incrementar={this.inc}/>
      </div>
    );
  }
}

export default Contador;
  • Dessa forma o componente filho Botoes já consegue receber via props essas funções, acessando as propriedades incrementar e decrementar.

  • Agora, vamos fazer a parte do componente PassoForm. Esse vai ser um pouco mais complicado, pois nele precisamos tanto passar informação quando receber(notificando o componente do que acabou de acontecer no passo). Primeiro, vamos pegar a trecho de código do formulário e migrar do Contador para o PassoForm:

import React from "react";

export default function PassoForm(props) {
  return (
    <div>
      <label htmlFor="passoInput">Passo: </label>
      <input 
        type="number" 
        value={props.passoForm} 
        id="passoInput"
        onChange={props.setPassoForm} 
      />
    </div>
  );
}
  • E vamos importar o PassoForm dentro do Contador e criar a referência onde ficava o formulário:
// [...]
import PassoForm from "./StepForm";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  // [...]

  setPasso = (e) => { 
    this.setState({
      passo: +e.target.value 
    })
  }

  render() {
    return (
      <div className="Contador">
        <h2>Contador</h2>
        <Display numeroDisplay={this.state.numero}/> 
        
        <PassoForm/>
        
        <Botoes decrementar={this.dec} incrementar={this.inc}/>
      </div>
    );
  }
}

export default Contador;
  • Agora, vamos passar como parâmetro para o PassoForm o que queremos como passo inicial:
// [...]
import PassoForm from "./StepForm";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  // [...]

  setPasso = (e) => { 
    this.setState({
      passo: +e.target.value 
    })
  }

  render() {
    return (
      <div className="Contador">
        <h2>Contador</h2>
        <Display numeroDisplay={this.state.numero}/> 
        
        <PassoForm passoForm={this.state.passo}/> {/* aqui temos uma comunicação direta: estamos passando via props os dados de um componente pai para o filho */}
        
        <Botoes decrementar={this.dec} incrementar={this.inc}/>
      </div>
    );
  }
}

export default Contador;
  • Agora, vamos precisar de uma comunicação indireta, onde o componente filho PassoForm vai passar para o componente pai Contador o dado que precisa ser alterado. Nesse caso, já não vamos mais receber o evento dentro de setPasso, vamos receber o novoPasso para deixarmos encapsulado esse evento dentro do componete PassoForm, e passarmos o número corredo do passo para alterar a variável de estado passo:
// [...]
import PassoForm from "./StepForm";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  // [...]

  setPasso = (novoPasso) => { 
    this.setState({
      passo: novoPasso 
    })
  }

  render() {
    return (
      <div className="Contador">
        <h2>Contador</h2>
        <Display numeroDisplay={this.state.numero}/> 
        
        <PassoForm passoForm={this.state.passo}/>
        
        <Botoes decrementar={this.dec} incrementar={this.inc}/>
      </div>
    );
  }
}

export default Contador;
  • Esse elemento já vai esperar que a propriedade setPassoForm do componente PassoForm receba via props a função setPasso:
// [...]
import PassoForm from "./StepForm";

class Contador extends Component {
  
  state = {
    numero: this.props.numeroInicial || 0, 
    passo: this.props.passoInicial || 5 
  }

  // [...]

  setPasso = (novoPasso) => { 
    this.setState({
      passo: novoPasso 
    })
  }

  render() {
    return (
      <div className="Contador">
        <h2>Contador</h2>
        <Display numeroDisplay={this.state.numero}/> 
        
        <PassoForm passoForm={this.state.passo} setPassoForm={this.setPasso}/>
        
        <Botoes decrementar={this.dec} incrementar={this.inc}/>
      </div>
    );
  }
}

export default Contador;
  • Então agora no PassoForm o evento onChange vai esperar props.setPasso, mas não podemos chamar direto, senão vai ser passado um evento, mas não queremos isso, já queremos passar o novo número, então vamos receber o evento e dentro dele vamos chamar props.setPasso passando o evento.target.value:
import React from "react";

export default function PassoForm(props) {
  return (
    <div>
      <label htmlFor="passoInput">Passo: </label>
      <input 
        type="number" 
        value={props.passoForm} 
        id="passoInput"
        onChange={e => props.setPassoForm(+e.target.value)} // "+" na frente para converter para int
      />
    </div>
  );
}
  • Podemos notar que na comunicação indireta, temos o componente pai enviando via props uma função para o componente filho, de tal forma que quando o evento acontece (onChange={e => props.setPassoForm(+e.target.value)}) o componente filho, manda de volta a informação do novoPasso que precisa ser alterado no estado (que é exatamente o que se encontra dentro de e.target.value[o atributo novoPasso que é recebido na função setPasso]).

Projeto calculadora

Criando o projeto

create-react-app calculadora
                 [nome-aplicação]
  • Ao finalizar a criação ele informa no terminal os próximos passos. Primeiro, entrar na pasta da aplicação:
cd calculadora
   [nome-pasta-aplicação]
  • Segundo, rodar o comando para iniciar o projeto:
npm start

Criando o Componente Calculator

A priori, vamos uma pasta dentro de src chamada main e dentro dela vamos criar o arquivo Calculator.jsx e seu arquivo css.

  • Esse componente Calculator vai ser baseado em classes. Vamos criar a extrutura base desse componente, importar o seu arquivo css e criar uma classe para esse elemento:
import React, { Component } from "react";

import "./Calculator.css";

export default class Calculator extends Component {
  render() {
    return (
      <div className="calculator">
        
      </div>
    )
  }
}
  • No arquivo css vamos chamar essa classe que criamos chamada calculator, e vamos inserir a altura, largura e uma borda para essa calculadora tendo como base a calculadora de um macbook:
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;
}
  • Vamos tratar os estouros do conteúdo com a propriedade overflow:
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;

  overflow: hidden; /*overflow: propriedade especifica se o conteúdo deve ser recortado ou adicionado barras de rolagem quando o conteúdo de um elemento for muito grande para caber na área especificada; hidden- O estouro é cortado e o restante do conteúdo ficará invisível*/
}
  • E para visualizar o espaço ocupado em tela pela calculadora, vamos inserir um backgroud temporário:
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;

  overflow: hidden; /*overflow: propriedade especifica se o conteúdo deve ser recortado ou adicionado barras de rolagem quando o conteúdo de um elemento for muito grande para caber na área especificada; hidden- O estouro é cortado e o restante do conteúdo ficará invisível*/

  background-color: red; /*temporário*/
}
  • Agora, vamos abrir o arquivo index.js do projeto calculadora, e dentro dele vamos remover o componente App que não vamos usá-lo nesse projeto e vamos importar o componente Calculator e referência-lo:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
import Calculator from './main/Calculator';
import reportWebVitals from './reportWebVitals';


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

reportWebVitals();
  • E salvando o arquivo já podemos visualizar nosso componente sendo renderizado em tela, mas ele não está centralizado.

  • Para resolver a posição do componente Calculator em tela, vamos fazer umas alterações no arquivo index.css(ele já está importado dentro do arquivo index.js):

body {
  display: flex;
  justify-content: center; /*para centralizar no eixo horizontal*/
  align-items: center; /*para centralizar no eixo vertical*/

  height: 100vh; /*vai ocupar a altura inteira da tela*/
}
  • E quando inserirmos texto, também queremos que eles fiquem centralizados, portanto vamos utilizar a propriedade text-aling:
body {
  display: flex;
  justify-content: center;
  align-items: center;

  height: 100vh;

  text-align: center;
  color: #fff; /*alterar a cor da fonte*/
}
  • E também, vamos aplicar um gradiente no fundo da tela:
body {
  display: flex;
  justify-content: center;
  align-items: center;

  height: 100vh;

  text-align: center;
  color: #fff;
  
  background: linear-gradient(to right, rgb(170, 75, 107), rgb(107, 107, 131), rgb(59, 141, 153));
}
  • Para a fonte ficar mais parecida com a calculadora modelo, vamos importar a fonte Roboto e aplicar ela em todos os elementos:
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@100&display=swap');

* {
  font-family: 'Roboto Mono', monospace;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;

  height: 100vh;

  text-align: center;
  color: #fff;
  
  background: linear-gradient(to right, rgb(170, 75, 107), rgb(107, 107, 131), rgb(59, 141, 153));
}
  • E por fim, vamos voltar no arquivo index.js e adicionar um título(h1) para a nossa calculadora. Lembrando que se não envolvermos os elementos adjacente com uma div ou com o React.Fragment, vai ocorrer um erro.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
import Calculator from './main/Calculator';
import reportWebVitals from './reportWebVitals';


ReactDOM.render(
  <React.StrictMode>
    <div>
      <h1>Calculadora</h1>
      <Calculator />
    </div>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

Criando o Componente Button

A priori, vamos uma pasta dentro de src chamada components e dentro dela vamos criar o arquivo Button.jsx e seu arquivo css.

  • Esse componente Button vai ser baseado em funções. Vamos criar a extrutura base desse componente, importar o seu arquivo css e criar uma classe para esse elemento:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button className="button">
      0 {/*A priori vamos colocar um número qualquer no botão para apenas visualizarmos em tela*/}
    </button>
  )
}
  • Agora dentro do componente principal Calculator vamos importar o componente Button e referência-lo:
import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";

export default class Calculator extends Component {
  render() {
    return (
      <div className="calculator">
        <Button />
        <Button />
        <Button />
        <Button />
        <Button />
      </div>
    )
  }
}
  • Tendo referênciado alguns botões, vamos aplicar o estilo para ficar o mais próximo possível do nosso modelo. A priori, dentro da pseudo classe :root vamos criar umas variáveis de CSS para simplificar nosso código:
:root { /*para criar variáveis css*/
  --background-button: #f0f0f0;
  --border-button: solid 1px #888;
}
  • E vamos utilizar essas variáveis a partir da função var nas bordas e no background dos botões:
.button {
  border: none; /*para remover bordas existentes*/
  border-right: var(--border-button);
  border-bottom: var(--border-button);

  background-color: var(--background-button);
}
  • Além disso, vamos alterar o tamanho da fonte e remover o outline azul que aparece quando passamos o cursor sobre o botão:
.button {
  font-size: 1.4rem;

  border: none;
  border-right: var(--border-button);
  border-bottom: var(--border-button);

  background-color: var(--background-button);

  outline: none; /*para remover o outline azul quando passamos o cursor sobre o button*/
}
  • Voltando no componente Button, vamos receber uma propriedade label, para receber o elemento/label do botão:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button className="button">
      {props.label}
    </button>
  )
}
  • E no componente Calculator vamos passar o label via props:
import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";

export default class Calculator extends Component {
  render() {
    return (
      <div className="calculator">
        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • Agora, vamos usar o CSS grid para organizar os botões. Dentro de Calculator.css, primeiro vamos remover o background red. Em seguida, vamos ativar o display: grid, para tornar o elemento um grid container(por padrão organiza os elementos dentro em coluna):
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;

  overflow: hidden; /*overflow: propriedade especifica se o conteúdo deve ser recortado ou adicionado barras de rolagem quando o conteúdo de um elemento for muito grande para caber na área especificada; hidden- O estouro é cortado e o restante do conteúdo ficará invisível*/
 
  display: grid; /*coloca os elementos um embaixo do outro usando o mesmo tamanho para todos os elementos*/
}
  • Podemos usar a propriedade grid-template-columns para determinar o número de colunas da nossa calculadora e organizá-la:
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;

  overflow: hidden;
 
  display: grid; 
  grid-template-columns: repeat(4, 25%); /*vamos ter 4 colunas, cada uma ocupando 25%; repeat age como um multiplicador, ou seja, ele aplica 4 vezes 25% nas colunas*/
}
  • No inicio da calculadora temos o display e em seguida, abaixo, cada uma das 5 linhas vão ocupar um tamanho fixo. Vamos usar a propriedade grid-template-rows para definir o tamanho dessas linhas:
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;

  overflow: hidden; 
 
  display: grid; 
  grid-template-columns: repeat(4, 25%); 
  grid-template-rows: 48px 48px 48px 48px 48px; /*48px para cada uma das 5 linhas dessa grid*/
}

Criando o Componente Display

A priori, dentro da pasta components vamos criar o arquivo Display.jsx e seu arquivo css.

  • Esse componente Display vai ser baseado em funções. Vamos criar a extrutura base desse componente, importar o seu arquivo css e criar uma classe para esse elemento:
import React from "react";
import "./Display.css"

export default function Display(props) {
  return (
    <div className="display">
      
    </div>
  )
}
  • Esse componente Display vai receber uma propriedade que vai ser o valor exibido:
import React from "react";
import "./Display.css"

export default function Display(props) {
  return (
    <div className="display">
      {props.value}
    </div>
  )
}
  • Agora dentro do componente principal Calculator vamos importar o componente Button e referência-lo:
import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";
import Display from "../componets/Display";

export default class Calculator extends Component {
  render() {
    return (
      <div className="calculator">
        <Display />

        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • A priori, vamos inserir um valor meramente ilustrativo para visualizarmos em tela:
import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";
import Display from "../componets/Display";

export default class Calculator extends Component {
  render() {
    return (
      <div className="calculator">
        <Display value={100}/>


        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • Agora indo no arquivo Calculator.css antes das 5 linhas fixas dos botões vamos inserir uma linha que vai representar o display:
.calculator {
  height: 320px;
  width: 235px;
  border-radius: 5px;

  overflow: hidden; 
 
  display: grid; 
  grid-template-columns: repeat(4, 25%);
  grid-template-rows: 1fr 48px 48px 48px 48px 48px; /*a primeira linha vai ser 1fr(um fragmento) que vai pegar o restante do tamanho disponivel em tela e 48px para cada uma das 5 linhas restante dessa grid*/
}
  • Vamos melhorar o display indo no arquivo Display.css. Primeiramente, vamos usar a propriedade grid-column para dá um span, para informar quantas colunas da grid o display vai ocupar, nesse caso, vão ser 4:
.display {
  grid-column: span 4; /*span 4: o span para informa quantas colunas da grid esse elemento vai ocupar, nesse caso vai ser 4*/
}
  • Vamos definir uma cor de fundo(cor preto com uma transparência):
.display {
  grid-column: span 4;

  background-color: #0004;
}
  • Embora o Display seja um elemento da grid, podemos aplicar o flexbox nele(apenas os elementos dentro do display vão receber essa propriedade):
.display {
  grid-column: span 4; 

  display: flex;

  background-color: #0004;
}
  • Podemos aplicar o justify-content para o elemento ficar a direita e centralizar o conteúdo com o align-items:
.display {
  grid-column: span 4; 

  display: flex;
  justify-content: flex-end;
  align-items: center;

  background-color: #0004;
}
  • E adicionar um espaçamento para tirar o elemento mais da borda:
.display {
  grid-column: span 4; 

  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 10px;

  background-color: #0004;
}
  • Por fim, vamos ajustar o tamanho da fonte, para que o valor do display fique maior em relação aos botões:
.display {
  font-size: 2.1rem; /*REM - é calculado em cima do valor da TAG Body, Html ou Navegador; EM - já utiliza no cálculo o valor do elemento pai, criando uma hierarquia no cálculo, tornando mais complexo o uso.*/

  grid-column: span 4; 

  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 10px;

  background-color: #0004;
}

Retonando no Componente Button - Retoques

  • Primeiramente, no arquivo Button.css vamos adicionar a pseudo classe active para quando clicarmos no botão ele mudar de cor:
:root { /*para criar variáveis css*/
  --background-button: #f0f0f0;
  --border-button: solid 1px #888;
}

.button {
  font-size: 1.4rem;

  border: none;
  border-right: var(--border-button);
  border-bottom: var(--border-button);

  background-color: var(--background-button);

  outline: none;
}

.button:active { /*para o botão ficar mais escuro ao clicar*/
  background-color: #ccc;
}
  • Agora, vamos criar classes para adicionarmos depois nos elementos button de acordo com o número de colunas que devem ocupar:
:root { /*para criar variáveis css*/
  --background-button: #f0f0f0;
  --border-button: solid 1px #888;
}

.button {
  font-size: 1.4rem;

  border: none;
  border-right: var(--border-button);
  border-bottom: var(--border-button);

  background-color: var(--background-button);

  outline: none;
}

.button:active { /*para o botão ficar mais escuro ao clicar*/
  background-color: #ccc;
}

.button.double { /*vamos aplicar essa propriedade em botões que devem ocupar duas posições*/
  grid-column: span 2;
}

.button.triple { /*vamos aplicar essa propriedade em botões que devem ocupar três posições*/
  grid-column: span 3;
}
  • Também vamos criar uma classe para adicionarmos nos elementos button quando for um operador, pois esse botões tem a cor de fundo e do texto diferente dos demais:
:root { /*para criar variáveis css*/
  --background-button: #f0f0f0;
  --border-button: solid 1px #888;
}

.button {
  font-size: 1.4rem;

  border: none;
  border-right: var(--border-button);
  border-bottom: var(--border-button);

  background-color: var(--background-button);

  outline: none;
}

.button:active { /*para o botão ficar mais escuro ao clicar*/
  background-color: #ccc;
}

.button.double { /*vamos aplicar essa propriedade em botões que devem ocupar duas posições*/
  grid-column: span 2;
}

.button.triple { /*vamos aplicar essa propriedade em botões que devem ocupar três posições*/
  grid-column: span 3;
}

.button.operation { /*vamos aplicar essa propriedade em botões de operações*/
  background-color: #fa8231;
  color: #fff;
}

.button.operation:active {
  background-color: #fa8231cc;
}
  • Agora vamos fazer com essas classes sejam aplicadas dentro dos botões de acordo com as propriedades forem passadas. No componente Button, no lugar da className="button" vamos definir uma expressão e ela delimitada por batik, ou seja, temos uma template string:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button className={` 
      
    `}>
      {props.label}
    </button>
  )
}
  • Vamos colocar logo a classe button, sabemos que ela sempre vai ser aplicada, independente de qualquer outra propriedade enviada:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button className={` 
      button
    `}>
      {props.label}
    </button>
  )
}
  • double, triple, operation vão precisar de porps para serem aplicadas ou não. Portanto, vamos adicionar um operador ternário, pois se a props double/triple/operation estiver definido "?" a classe double/triple/operation vai ser aplicada, senão ":" vai ficar vazio:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    // dentro de uma tamplate string(javascript puro) temos que usar o "$" e chaves{} para interpolar uma variálvel
    <button className={` 
      button
      ${props.double ? 'double' : ''} 
      ${props.triple ? 'triple' : ''}
      ${props.operation ? 'operation' : ''} 
    `}> 
      {props.label}
    </button>
  )
}
  • Antes de usarmos essas classes dentro do componente Calculator vai ter uma questão importante. No botão temos o evento do click, e quando clicarmos no botão quermos que o conteúdo/label do botão seja retornado, então para isso vamos adicionar o onClick que vai disparar uma arrow function que recebe o evento, e vai chamar, props.click(), ou seja, esperamos receber via props do button uma propriedade chamada click que vai conter uma função dentro dela:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button onClick={e => props.click()} className={` 
      button
      ${props.double ? 'double' : ''}
      ${props.triple ? 'triple' : ''}
      ${props.operation ? 'operation' : ''} 
    `}>
      {props.label}
    </button>
  )
}
  • Só que essa função ao invés de receber o evento, vai receber diretamente o props.label, ou seja, o conteúdo do elemento, e ele será passado para a função click:
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button onClick={e => props.click(props.label)} className={` 
      button
      ${props.double ? 'double' : ''}
      ${props.triple ? 'triple' : ''}
      ${props.operation ? 'operation' : ''} 
    `}>
      {props.label}
    </button>
  )
}
  • Devemos ter cuidado para verificarmos se de fato essa propriedade está presente, portanto podemos colocar uma condicional, para primeiro verificarmos se a propriedade click está presente e depois chamar a função click():
import React from "react";

import "./Button.css";

export default function Button(props) {
  return (
    <button onClick={e => props.click && props.click(props.label)} className={` 
      button
      ${props.double ? 'double' : ''}
      ${props.triple ? 'triple' : ''}
      ${props.operation ? 'operation' : ''} 
    `}>
      {props.label}
    </button>
  )
}
  • Agora, voltando no componente Calculator vamos criar 3 funções: uma vai ser chamada para zerar a calculadora(pelo botão AC), outra função vai ser responsável por adicionar um digito(quando clicarmos nos botões numerais e o ponto) e a última vai ser responsável por setar a operação.

  • Vamos criar efetivamente a função que vai ser responsável por zerar a calculadora, ela vai se chamar clearMemory(). A priori, só vamos inserir um console.log para verficarmos no console se a função está sendo chamada:

import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";
import Display from "../componets/Display";

export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  render() {
    return (
      <div className="calculator">
        <Display value={100}/>

        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • A segunda função, responsável por setar a operação vai se chamar setOperation e vai receber como parâmetro a operação. E a priori, vamos colocar para ela apenas retornar no console a operation recebida:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation)
  } 

  render() {
    return (
      <div className="calculator">
        <Display value={100}/>

        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • Por fim, a próxima função vai ser responsável por adicionar um digito e ela vai se chamar addDigit e vai receber como parâmetro um digito n. E a priori, vamos colocar para ela apenas retornar no console o digito recebido:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation)
  }
  
  addDigit(n) {
    console.log(n);
  }

  render() {
    return (
      <div className="calculator">
        <Display value={100}/>

        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • Então, para que click do button seja chamada essas funções vamos usar arrow function. E vamos colocar essas arrow function dentro diretamente do render, pois vamos usar elas em vários botões. Primeiramente, vamos criar uma constante chamada addDigit que vai receber uma arrow function que vai receber um n e invoca this.addDigit(esse this de fato, dentro do render representa o objeto atual, então estamos chamando a função addDigit) passando esse n:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    const addDigit = n => this.addDigit(n); // criamos uma função arrow com o mesmo nome, essa função vai garantir que o this seja chamado corretamente

    return (
      <div className="calculator">
        <Display value={100}/>

        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • Vamos fazer o mesmo para a função setOperation:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {
    
    const setOperation = op => this.setOperation(operation);
    const addDigit = n => this.addDigit(n);
    
    return (
      <div className="calculator">
        <Display value={100}/>

        <Button label="AC"/>
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • Já a function clearMemory como vamos chamá-la apenas uma vez no button AC, vamos colocá-la diretamente:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    const addDigit = n => this.addDigit(n);
    const setOperation = op => this.setOperation(op);

    return (
      <div className="calculator">
        <Display value={100}/>

        <Button label="AC" click={() => this.clearMemory()}/> {/*O evento click recebe uma arrow function apontando para this.clearMemory*/}
        <Button label="/"/>
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*"/>
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-"/>
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+"/>
        <Button label="0"/>
        <Button label="."/>
        <Button label="="/>
      </div>
    )
  }
}
  • O próximo passo é nas label que são uma operação, vamos chamar o evento click e chamar a constante setOperation que recebeu uma arrow funcion que aponta para a função principal this.setOperation. Como já criamos a constante com arrow function podemos chamar a constante diretamente:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    const addDigit = n => this.addDigit(n);
    const setOperation = op => this.setOperation(op);

    return (
      <div className="calculator">
        <Display value={100} />

        <Button label="AC" click={() => this.clearMemory()} /> 
        <Button label="/" click={setOperation} />
        <Button label="7"/>
        <Button label="8"/>
        <Button label="9"/>
        <Button label="*" click={setOperation} />
        <Button label="4"/>
        <Button label="5"/>
        <Button label="6"/>
        <Button label="-" click={setOperation} />
        <Button label="1"/>
        <Button label="2"/>
        <Button label="3"/>
        <Button label="+" click={setOperation} />
        <Button label="0"/>
        <Button label="."/>
        <Button label="=" click={setOperation} />
      </div>
    )
  }
}
  • Agora podemos fazer o mesmo nas label que são um digito, vamos chamar o evento click e chamar a constante addDigit que recebeu uma arrow funcion que aponta para a função principal this.addDigit. Como já criamos a constante com arrow function podemos chamar a constante diretamente:
export default class Calculator extends Component {

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    const addDigit = n => this.addDigit(n);
    const setOperation = op => this.setOperation(op);

    return (
      <div className="calculator">
        <Display value={100} />

        <Button label="AC" click={() => this.clearMemory()} /> 
        <Button label="/" click={setOperation} />
        <Button label="7" click={addDigit} />
        <Button label="8" click={addDigit} />
        <Button label="9" click={addDigit} />
        <Button label="*" click={setOperation} />
        <Button label="4" click={addDigit} />
        <Button label="5" click={addDigit} />
        <Button label="6" click={addDigit} />
        <Button label="-" click={setOperation} />
        <Button label="1" click={addDigit} />
        <Button label="3" click={addDigit} />
        <Button label="+" click={setOperation} />
        <Button label="0" click={addDigit} />
        <Button label="." click={addDigit} />
        <Button label="=" click={setOperation} />
      </div>
    )
  }
}
  • Vale resaltar que só criamos as contantes que recebem as arrow function para resolver o this chamar o objeto atual. Temos duas formas de resolver isso, da forma que fizemos acima criando a arrow function ou criando um construtor e fazer o bind uma única vez, desse modo podemos chamar o this e a function diretamente:
export default class Calculator extends Component {

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    // const addDigit = n => this.addDigit(n);
    // const setOperation = op => this.setOperation(op);

    return (
      <div className="calculator">
        <Display value={100} />

        <Button label="AC" click={this.clearMemory} /> 
        <Button label="/" click={this.setOperation} />
        <Button label="7" click={this.addDigit} />
        <Button label="8" click={this.addDigit} />
        <Button label="9" click={this.addDigit} />
        <Button label="*" click={this.setOperation} />
        <Button label="4" click={this.addDigit} />
        <Button label="5" click={this.addDigit} />
        <Button label="6" click={this.addDigit} />
        <Button label="-" click={this.setOperation} />
        <Button label="1" click={this.addDigit} />
        <Button label="2" click={this.addDigit} />
        <Button label="3" click={this.addDigit} />
        <Button label="+" click={this.setOperation} />
        <Button label="0" click={this.addDigit} />
        <Button label="." click={this.addDigit} />
        <Button label="=" click={this.setOperation} />
      </div>
    )
  }
}
  • Para finalizar, dentro do button AC vamos aplicar a propriedade triple(automáticamente o button vai passar a ocupar três colunas, não precisamos passar o valor, basta a propriedade). No button 0 vamos aplicar a propriedade double. Por fim, todos os button que representão operações vamos aplicar a props operation:
export default class Calculator extends Component {

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    return (
      <div className="calculator">
        <Display value={100} />

        <Button label="AC" click={this.clearMemory} triple/> 
        <Button label="/" click={this.setOperation} operation/>
        <Button label="7" click={this.addDigit} />
        <Button label="8" click={this.addDigit} />
        <Button label="9" click={this.addDigit} />
        <Button label="*" click={this.setOperation} operation/>
        <Button label="4" click={this.addDigit} />
        <Button label="5" click={this.addDigit} />
        <Button label="6" click={this.addDigit} />
        <Button label="-" click={this.setOperation} operation/>
        <Button label="1" click={this.addDigit} />
        <Button label="2" click={this.addDigit} />
        <Button label="3" click={this.addDigit} />
        <Button label="+" click={this.setOperation} operation/>
        <Button label="0" click={this.addDigit} double/>
        <Button label="." click={this.addDigit} />
        <Button label="=" click={this.setOperation} operation/>
      </div>
    )
  }
}

Implementando a lógica da calculadora

  • Criar o estado inicial para depois manipularmos. Fora da classe, vamos definir uma constante initialState que vai receber as seguintes propriedades: displayValue vai ser o valor a ser exibido no display da calculadora; clearDisplay propriedade que vai informar se precisa ou não limpar o display, inicialmente vai começar com o valor booleano false; operation variável que vai armazenar a operação corrente; values array com dois valores, pois no momento que colocamos o valor e em seguida clica na operação, na próxima vez ele vai limpar o display e vai armazenar o segundo valor; current propriedade para verificar se estamos manipulando value de indice 0 do array ou o value de indice 1, vai iniciar com o valor 0:
import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";
import Display from "../componets/Display";

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
}
export default class Calculator extends Component {

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    return (
      <div className="calculator">
        <Display value={100} />

        <Button label="AC" click={this.clearMemory} triple/> 
        <Button label="/" click={this.setOperation} operation/>
        <Button label="7" click={this.addDigit} />
        <Button label="8" click={this.addDigit} />
        <Button label="9" click={this.addDigit} />
        <Button label="*" click={this.setOperation} operation/>
        <Button label="4" click={this.addDigit} />
        <Button label="5" click={this.addDigit} />
        <Button label="6" click={this.addDigit} />
        <Button label="-" click={this.setOperation} operation/>
        <Button label="1" click={this.addDigit} />
        <Button label="2" click={this.addDigit} />
        <Button label="3" click={this.addDigit} />
        <Button label="+" click={this.setOperation} operation/>
        <Button label="0" click={this.addDigit} double/>
        <Button label="." click={this.addDigit} />
        <Button label="=" click={this.setOperation} operation/>
      </div>
    )
  }
}
  • Criamos essa constante initialState, pois a função clearMemory vai chamar ela e voltar o display estado inicial, além de que o state vai fazer um clone desse objeto. Por isso, foi criada de forma separada para não ser alterada diretamente. Então, dentro da classe podemos criar a variável state para startar o estado, recebendo tudo que a variável inicialState possui dentro dela:
const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
}

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    console.log('limpar');
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Além de que, caso o clearMemory seja invocado vai chamar a função this.setState e atribuir os valores de initialState, valtando o estado para o inicio:
const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
}

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    return (
      // [...]
    )
  }
}
  • O próximo passo, é fazer com que o Display aponte para o value state e não mais para um valor fixo. Portanto, vamos inserir em value o valor this.state.displayValue:
const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
}

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    console.log(n);
  }

  render() {

    return (
      <div className="calculator">
        <Display value={this.state.displayValue} />

        <Button label="AC" click={this.clearMemory} triple/> 
        <Button label="/" click={this.setOperation} operation/>
        <Button label="7" click={this.addDigit} />
        <Button label="8" click={this.addDigit} />
        <Button label="9" click={this.addDigit} />
        <Button label="*" click={this.setOperation} operation/>
        <Button label="4" click={this.addDigit} />
        <Button label="5" click={this.addDigit} />
        <Button label="6" click={this.addDigit} />
        <Button label="-" click={this.setOperation} operation/>
        <Button label="1" click={this.addDigit} />
        <Button label="2" click={this.addDigit} />
        <Button label="3" click={this.addDigit} />
        <Button label="+" click={this.setOperation} operation/>
        <Button label="0" click={this.addDigit} double/>
        <Button label="." click={this.addDigit} />
        <Button label="=" click={this.setOperation} operation/>
      </div>
    )
  }
}
  • Agora, vamos implementar condicionais na função addDigit. Se a calculadora receber um digito ponto "."(n === ".") e o valor que está no display(this.state.displayValue) já contém um ponto(includes(".")), significa que não podemos adicionar um segundo ponto nesse número, essa tentativa vai ser ignorada e o código vai seguir(return):
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }
  }

  render() {

    return (
      // [...]
    )
  }
}

-O próximo passo dentro da função addDigit é criar uma variável chamada clearDisplay. A constante clearDisplay vai ter dois cenários. O primeiro cenário vai ser quando o display conter apenas o digito 0, pois a medida que adicionarmos o digito diferente de 0 vai limpar o display e ir concatenando os digitos. A segunda situação é quando a variável clearDisplay do objeto State(que clonou o objeto initialState) for true:

// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.states.clearDisplay // a constante vai receber um valor booleano, se o valor no display for igual a 0(true); OU o valor da variável clearDisplay do objeto state for true... clearDisplay vai ser true, caso nenhuma das condições seja true, vai receber false
  }

  render() {

    return (
      // [...]
    )
  }
}
  • O próximo passo, é criar uma constante chamada currentValue e ela vai depender da variável clearDisplay... vai depender se o display vai ser limpo ou não. Se o display for ser limpo, ou seja, clearDisplay for true o valor currentValue vai ser vazio. Se o display não for ser limpo (senão), ou seja, clearDisplay for false, currentValue vai receber o valor atual que está no display(this.state.displayValue). Vamos usar o operador ternário:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.states.clearDisplay

    const currentValue = clearDisplay ? '' : this.state.displayValue;
  }

  render() {

    return (
      // [...]
    )
  }
}
  • A próxima constante a ser criada vai ser o novo valor que vamos colocar no display, ela vai ser chamada de displayValue que vai receber o currentValue + o digito n que for clicado:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.states.clearDisplay

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Agora, o próximo passo é mudarmos o estado da aplicação, por hora quando clicamos nos digitos não ocorre nada no valor do display, portanto vamos chamar a função this.setState e passar como valores o displayValue e o clearDisplay recebendo false (pois uma vez que digitamos o valor e já limpamos o display com a variável clearDisplay igual a true, alteramos clearDisplay para false):
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay // a constante vai receber um valor booleano, se o valor no display for igual a 0(true); OU o valor da variável clearDisplay do objeto state for true... clearDisplay vai ser true, caso nenhuma das condições seja true, vai receber false

    const currentValue = clearDisplay ? '' : this.state.displayValue; 

    const displayValue = currentValue + n; // OU newDisplayValue

    this.setState({ displayValue, clearDisplay: false }); // Aqui receberia CHAVE: VALOR => this.setState({ displayValue: newDisplayValue, ...}); Mas colocamos a chave com o mesmo valor que está dentro do state que quando passarmos para o setState usamos apenas o nome do atributo que já reflete no nome da chave que é o mesmo;
  }

  render() {

    return (
      // [...]
    )
  }
}
  • O próximo passo é inserirmos uma condicional para que sempre que for digitado um valor diferente de ponto, ou seja, um número. Queremos armazenar esse número digitado no array values do objeto state, só que o valor que contém no displayValue é uma string, e queremos armazenar no array como float.
    Dentro da condicional if se for digitado qualquer valor diferente de ponto, vai ser armazenado na contante i o indice do array que estamos trabalhando atualmente:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay 

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;

    this.setState({ displayValue, clearDisplay: false });

    if (n !== ".") {
      const i = this.state.current;
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • E agora podemos criar uma constante chamada newValue para convertermos o valor do displayValue(contém o valor do valor corrente do display + valor n que foi recebido na função) para um float:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay 

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;

    this.setState({ displayValue, clearDisplay: false });

    if (n !== ".") {
      const i = this.state.current;
      const newValue = parseFloat(displayValue);
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Desse modo, podemos criar uma nova constante chamada values que vai receber o clone do array values do objeto state:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay 

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;

    this.setState({ displayValue, clearDisplay: false });

    if (n !== ".") {
      const i = this.state.current;
      const newValue = parseFloat(displayValue);
      const values = [...this.state.values];
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Assim, podemos fazer com que o array values receba os valores de acordo com o indice:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay 

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;

    this.setState({ displayValue, clearDisplay: false });

    if (n !== ".") {
      const i = this.state.current;
      const newValue = parseFloat(displayValue);
      const values = [...this.state.values];
      values[i] = newValue;
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Por fim, uma vez que mudamos os valores do array, vamos adicionar esse array no estado do objeto:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    console.log(operation);
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay 

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;

    this.setState({ displayValue, clearDisplay: false });

    if (n !== ".") {
      const i = this.state.current;
      const newValue = parseFloat(displayValue);
      const values = [...this.state.values];
      values[i] = newValue;
      this.setState({ values });
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Por hora, nossa calculadora só está armazenado o valor do índice 0 do array values. Agora vamos implementar a segunda parte da lógica, para que quando clicarmos na operação temos que passar a armazenar os valores no índice 1 do array. Primeiramente, na função setOperation se o valor current for igual a 0 temos que mudá-lo para 1, para começarmos a mexer no segundo valor do array. Além disso, temos que mudar a variável clearDisplay para true, para que o display seja limpo, pois depois que setarmos uma operação, os próximos digitos já vão pertencer a outro valor. O terceiro valor que precisamos alterar o estado é a operation que vamos receber nessa função setOperation:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    }
  } 

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay 

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n;

    this.setState({ displayValue, clearDisplay: false });

    if (n !== ".") {
      const i = this.state.current;
      const newValue = parseFloat(displayValue);
      const values = [...this.state.values];
      values[i] = newValue;
      this.setState({ values });
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Agora, na próxima vez que clicarmos no igual ou em outra operação, significa que ele vai pegar esses dois valores, processar, gerar o resultado e armazená-lo no primeiro elemento do array(índice 0), limpa o elemento de índice 1 para ficar livre para receber outro valor. Quando clicamos em uma operação, a função setOperation é chamada e caso não estivermos mexendo no primeiro elemento do array(índice 0), vamos criar a lógica para concluir a operação(else):
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {

    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • E dentro do else vamos gerar o resultado, caso o usuário clique no operador "=". Para isso, vamos criar uma constante chamada equal que vai receber o valor true caso o operador que o usuário clicou seja igual a "=":
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Outro caso a ser tratado é quando já temos o v1, a operação e o v2, e ao invés de clicarmos no igual para finalizar a operação, clicamos em outro operado. Portanto, os dois primeiros valores e a sua respectiva operação vão ter que ser processados e armazenado o resultado no índice 0. O primeiro ponto que devemos dar atenção, é quando clicamos na operação, sabemos que ela chama a função setOperation e armazena a operação, mas como já temos a operação anterior, nesse caso precisamos pegar essa operation já armazenada. Para isso, vamos salvar ela em uma constante chamada currentOperation:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
      const currentOperation = this.state.operation;
    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • E agora vamos criar um clone do array atual values em uma variável chamada values e em seguida fazer o calculo do valor "em cima" da função eval e inserir na primeira possição desse novo array. Vamos usar a função eval, mas também poderiamos usar um switch case ou if else de acordo com a operação digitada, mas para evitar o código ficar muito grande podemos usar essa função. Além disso, não podemos esquecer que o resultado dessa operação deve ser armazenado na posição de índice 0 desse array, e a posição de índice 1 deve ser zerada:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
      const currentOperation = this.state.operation;

      const values = [...this.state.values];
      values[0] = eval(`${values[0]} ${currentOperation} ${values[1]}`);
      values[1] = 0;
    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • E para que tudo isso reflita no estado da aplicação, vamos chamar a função setState e passar os novos valores. O displayValue vai receber o valor do índice 0, para exibir no display o resultado da primeira operação; Já o operation se for "="(equal) a operation vai receber null, ou seja, essa operação vai ser concluida, se não(vai ser outra operação) operation vai receber a nova operação; O valor current se o usuário clicou no "="(equal) vai continuar com o índice 0, senão(no caso vai ser outra operação) o usuário vai passar a mexer no índice 1; Outro ponto é, se o usuário clicou em "="(equal) não vai precisar limpar o display(clearDisplay: false), só vai ser necessário limpar o display se for outra operação(ou seja, diferente de equal); Por fim, precisamos passar o array values para alterar o seu estado:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
      const currentOperation = this.state.operation;

      const values = [...this.state.values];
      values[0] = eval(`${values[0]} ${currentOperation} ${values[1]}`);
      values[1] = 0;

      this.setState({
        displayValue: values[0],
        operation: equal ? null : operation,
        current: equal ? 0 : 1,
        clearDisplay: !equal, // se for diferente de equal retorna true, se for igual retorna false
        values
      });
    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Podemos notar que o próprio React gera uma advertência a respeito do uso da função eval, para capturar os erros podemos usar o try catch:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
      const currentOperation = this.state.operation;

      const values = [...this.state.values];
      try {
        values[0] = eval(`${values[0]} ${currentOperation} ${values[1]}`);
      } catch (error) {
        values[0] = this.state.values[0]; // se der um erro vamos pegar o valor atual do estado 
      }
      values[1] = 0;

      this.setState({
        displayValue: values[0],
        operation: equal ? null : operation,
        current: equal ? 0 : 1,
        clearDisplay: !equal, // se for diferente de equal retorna true, se for igual retorna false
        values
      });
    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Ainda sim, o mais recomendado é substituir por um switch case ou if else para executar a operação de acordo com a operação digitada:
// [...]

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
      const currentOperation = this.state.operation;

      const values = [...this.state.values];
      // try {
      //   values[0] = eval(`${values[0]} ${currentOperation} ${values[1]}`);
      // } catch (error) {
      //   values[0] = this.state.values[0]; // se der um erro vamos pegar o valor atual do estado 
      // }

      switch (currentOperation) {
        case "-": 
          values[0] = values[0] - values[1]
          break;
        case "*": 
          values[0] = values[0] * values[1]
          break;
        case "/": 
          values[0] = values[0] / values[1]
          break;
        default:
          values[0] = values[0] + values[1]
          break;
      }
      values[1] = 0;

      this.setState({
        displayValue: values[0],
        operation: equal ? null : operation,
        current: equal ? 0 : 1,
        clearDisplay: !equal, // se for diferente de equal retorna true, se for igual retorna false
        values
      });
    }
  } 

  addDigit(n) {
    // [...]
    }
  }

  render() {

    return (
      // [...]
    )
  }
}
  • Finalizando o nosso componente Calculator assim:
import React, { Component } from "react";

import "./Calculator.css";
import Button from "../componets/Button";
import Display from "../componets/Display";

const initialState = {
  displayValue: '0',
  clearDisplay: false,
  operation: null,
  values: [0, 0],
  current: 0
};

export default class Calculator extends Component {

  state = { ...initialState };

  constructor(props) {
    super(props)

    this.clearMemory = this.clearMemory.bind(this);
    this.setOperation = this.setOperation.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clearMemory() {
    this.setState({ ...initialState });
  }

  setOperation(operation) {
    if (this.state.current === 0) {
      this.setState({ operation, current: 1, clearDisplay: true });
    } else {
      const equal = operation === "=";
      const currentOperation = this.state.operation;

      const values = [...this.state.values];
      switch (currentOperation) {
        case "-": 
          values[0] = values[0] - values[1]
          break;
        case "*": 
          values[0] = values[0] * values[1]
          break;
        case "/": 
          values[0] = values[0] / values[1]
          break;
        default:
          values[0] = values[0] + values[1]
          break;
      }
      values[1] = 0;

      this.setState({
        displayValue: values[0],
        operation: equal ? null : operation,
        current: equal ? 0 : 1,
        clearDisplay: !equal, // se for diferente de equal retorna true, se for igual retorna false
        values
      });
    }
  }  

  addDigit(n) {
    if (n === "." && this.state.displayValue.includes(".")) {
      return
    }

    const clearDisplay = this.state.displayValue === "0" 
      || this.state.clearDisplay
    
    const currentValue = clearDisplay ? '' : this.state.displayValue;

    const displayValue = currentValue + n; // OU newDisplayValue

    this.setState({ displayValue, clearDisplay: false }); // Aqui receberia CHAVE: VALOR => this.setState({ displayValue: newDisplayValue, ...}); Mas colocamos a chave com o mesmo valor que está dentro do state que quando passarmos para o setState usamos apenas o nome do atributo que já reflete no nome da chave que é o mesmo;

    if (n !== ".") {
      const i = this.state.current;
      const newValue = parseFloat(displayValue);
      const values = [...this.state.values];
      values[i] = newValue;
      this.setState({ values });
    }

  }

  render() {

    return (
      <div className="calculator">
        <Display value={this.state.displayValue} />

        <Button label="AC" click={this.clearMemory} triple/> 
        <Button label="/" click={this.setOperation} operation/>
        <Button label="7" click={this.addDigit} />
        <Button label="8" click={this.addDigit} />
        <Button label="9" click={this.addDigit} />
        <Button label="*" click={this.setOperation} operation/>
        <Button label="4" click={this.addDigit} />
        <Button label="5" click={this.addDigit} />
        <Button label="6" click={this.addDigit} />
        <Button label="-" click={this.setOperation} operation/>
        <Button label="1" click={this.addDigit} />
        <Button label="2" click={this.addDigit} />
        <Button label="3" click={this.addDigit} />
        <Button label="+" click={this.setOperation} operation/>
        <Button label="0" click={this.addDigit} double/>
        <Button label="." click={this.addDigit} />
        <Button label="=" click={this.setOperation} operation/>
      </div>
    )
  }
}