One place for hosting & domains

      Como personalizar os componentes React com props


      O autor selecionou a Creative Commons para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Neste tutorial você criará componentes personalizados passando props (que significa propriedades) aos seus componentes. Props são argumentos que você fornece a um elemento JSX. Elas se parecem com os props padrão do HTML, mas não são pré-definidos e podem ter diferentes tipos de dados JavaScript, incluindo números, strings, funções, matrizes e até mesmo outros componentes React. Seus componentes personalizados utilizam props para exibir dados ou utilizá-los para fazer os componentes interagirem. Os props são parte fundamental da criação de componentes adaptáveis para situações diferentes. Aprender como usá-los dará a você as ferramentas necessárias para desenvolver componentes personalizados para lidar com situações únicas.

      Após adicionar os props aos componentes, você utilizará o PropTypes para definir o tipo de dados que você espera que um componente receba. O PropTypes é um sistema de tipos simples que verifica se os dados correspondem aos tipos esperados no ambiente de execução. Ele serve como uma documentação e verificador de erros que ajudará você a manter seu aplicativo previsível à medida que ele for escalonado.

      Ao final deste tutorial, você utilizará uma variedade de props para desenvolver um aplicativo pequeno que receberá uma matriz de dados de animais. Ela mostrará informações como nome, nome científico, tamanho, dieta e outras informações.

      Nota: o primeiro passo define um projeto em branco no qual você desenvolverá o exercício do tutorial. Se você já tiver um projeto de trabalho e deseja ir diretamente ao trabalho com os props, vá para o Passo 2.

      Pré-requisitos

      Passo 1 — Criando um projeto vazio

      Neste passo, você criará um novo projeto utilizando o Create React App. Em seguida, você excluirá o projeto de exemplo e os arquivos relacionados que foram instalados durante a inicialização dele. Por fim, você criará uma estrutura de arquivos simples para organizar seus componentes.

      Para começar, crie um novo projeto. Em sua linha de comando, execute o script a seguir para instalar um novo projeto usando o create-react-app:

      • npx create-react-app prop-tutorial

      Após o projeto ser finalizado, vá para o diretório:

      Em uma nova guia ou janela do terminal, inicie o projeto usando o script start do Create React App. O navegador irá atualizar automaticamente com as alterações. Dessa forma, deixe este script em execução durante todo o tempo em que você estiver trabalhando:

      Você receberá um servidor local em execução. Se o projeto não abriu na janela de um navegador, você pode abri-lo indo para http://localhost:3000/. Se o estiver executando em um servidor remoto, o endereço será http://your_domain:3000.

      Seu navegador carregará com um aplicativo simples do React, que vem incluído como parte do Create React App:

      Projeto modelo do React

      Você desenvolverá um conjunto completamente novo de componentes personalizados. Você começará seu projeto removendo um pouco do código boilerplate para que você possa ter um projeto vazio.

      Para iniciar, abra o src/App.js em um editor de texto. Esse é o componente raiz que é injetado na página. Todos os componentes iniciarão a partir daqui. Saiba mais sobre o App.js em Como configurar um projeto React com o Create React App.

      Abra o src/App.js com o seguinte comando:

      Você verá um arquivo como este:

      prop-tutorial/src/App.js

      import React from 'react';
      import logo from './logo.svg';
      import './App.css';
      
      function App() {
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <p>
                Edit <code>src/App.js</code> and save to reload.
              </p>
              <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Learn React
              </a>
            </header>
          </div>
        );
      }
      
      export default App;
      

      Exclua a linha import logo from './logo.svg';. Em seguida, substitua tudo na instrução return para retornar um conjunto de tags vazias: <></>. Isso dará a você uma página de validação que não retorna nada. O código final ficará parecido com este:

      prop-tutorial/src/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        return <></>;
      }
      
      export default App;
      

      Salve e saia do editor de texto.

      Por fim, exclua o logo. Você não o usará neste seu aplicativo e é aconselhável remover arquivos não utilizados durante o trabalho. Isso evitará confusões futuras.

      Na janela do terminal, digite o seguinte comando:

      Se você olhar em seu navegador, verá uma tela vazia.

      tela vazia no chrome

      Com o projeto de exemplo do Create React App limpo, crie uma estrutura de arquivos simples. Isso ajudará você a manter os componentes isolados e independentes.

      Crie um diretório chamado components no diretório src. Ele terá todos os seus componentes personalizados.

      Cada componente terá o próprio diretório para armazenar o arquivo de componente com os estilos, imagens (se houver) e testes.

      Crie um diretório para o App:

      Mova todos os arquivos App para esse diretório. Use o curinga * para selecionar quaisquer arquivos que comecem com App., independentemente da extensão. Depois, utilize o comando mv para colocá-los no novo diretório.

      • mv src/App.* src/components/App

      Por fim atualize o caminho de importação relativo em index.js, que é o componente raiz que inicializa todo o processo.

      A instrução de importação precisa apontar para o arquivo Apps.js no diretório App. Portanto, faça a seguinte alteração destacada:

      prop-tutorial/src/index.js

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      import App from './components/App/App';
      import * as serviceWorker from './serviceWorker';
      
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        document.getElementById('root')
      );
      
      // If you want your app to work offline and load faster, you can change
      // unregister() to register() below. Note this comes with some pitfalls.
      // Learn more about service workers: https://bit.ly/CRA-PWA
      serviceWorker.unregister();
      

      Salve e saia do arquivo.

      Agora que o projeto está configurado, crie o primeiro componente.

      Neste passo, você criará um componente que será modificado com base nas informações de entrada, chamadas props. Os props são argumentos que você passa para uma função ou classe. Como seus componentes são transformados em objetos semelhantes aos HTML pelo JSX, você passará os props como se fossem atributos do HTML. Diferente dos elementos HTML, você pode passar vários tipos de dados diferentes: strings, matrizes. objetos e até mesmo funções.

      Aqui, você criará um componente que exibirá as informações de animais. Esse componente terá o nome e nome científico do animal como strings, o tamanho como um número inteiro, a dieta será uma matriz de strings e as informações adicionais serão um objeto. Você passará as informações para o novo componente como props e as consumirá no componente.

      Ao final deste passo, você terá um componente que consumirá diferentes props. Você também reutilizará o componente para exibir uma matriz de dados usando um componente comum.

      Adicionando dados

      Primeiro, serão necessários dados de exemplo. Crie um arquivo no diretório src/App chamado data.

      • touch src/components/App/data.js

      Abra o novo arquivo em seu editor de texto:

      • nano src/components/App/data.js

      Em seguida, adicione uma matriz de objetos que será usada como exemplo:

      prop-tutorial/src/components/App/data.js

      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: 140,
          diet: ['meat'],
        },
        {
          name: 'Gorilla',
          scientificName: 'Gorilla beringei',
          size: 205,
          diet: ['plants', 'insects'],
          additional: {
            notes: 'This is the eastern gorilla. There is also a western gorilla that is a different species.'
          }
        },
        {
          name: 'Zebra',
          scientificName: 'Equus quagga',
          size: 322,
          diet: ['plants'],
          additional: {
            notes: 'There are three different species of zebra.',
            link: 'https://en.wikipedia.org/wiki/Zebra'
          }
        }
      ]
      

      A matriz de objetos contém uma variedade de dados e dará a você a oportunidade de testar uma variedade de props. Cada objeto é um animal separado com o nome dele, o nome científico, o tamanho, a dieta e um campo opcional chamado additional que contém links ou anotações. Neste código você também exportou a matriz como default.

      Salve e saia do arquivo.

      Criando componentes

      Em seguida, crie um componente de espaço reservado chamado AnimalCard. Este componente eventualmente utilizará props e exibirá os dados.

      Primeiro, crie um diretório em src/components chamado AnimalCard. Em seguida, use o comando touch para criar um arquivo chamado src/components/AnimalCard/AnimalCard.js e um arquivo CSS chamado src/components/AnimalCard/AnimalCard.css.

      • mkdir src/components/AnimalCard
      • touch src/components/AnimalCard/AnimalCard.js
      • touch src/components/AnimalCard/AnimalCard.css

      Abra o AnimalCard.js no seu editor de texto:

      • nano src/components/AnimalCard/AnimalCard.js

      Adicione um componente básico que importa o CSS e retorna uma tag <h2>.

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard() {
        return <h2>Animal</h2>
      }
      

      Salve e saia do arquivo. Agora, é necessário que você importe os dados e os componentes em seu componente base App.

      Abra o src/components/App/App.js:

      • nano src/components/App/App.js

      Importe os dados e os componentes. Em seguida, faça um loop com os dados para retornar os componentes para cada item na matriz:

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      import data from './data';
      import AnimalCard from '../AnimalCard/AnimalCard';
      import './App.css';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {data.map(animal => (
              <AnimalCard key={animal.name}/>
            ))}
          </div>
        )
      }
      
      export default App;
      

      Salve e saia do arquivo. Aqui,utilize o método de matriz .map() para iterar os dados. Além da adição deste loop, você também terá uma div de quebra automática com uma classe que você utilizará para aplicação de estilo e uma tag <h1> para rotular seu projeto.

      Quando você salvar, o navegador será recarregado e você verá um rótulo para cada cartão.

      Projeto React no navegador sem estilo

      Em seguida, vamos aplicar o estilo para alinhar os itens. Abra o App.css:

      • nano src/components/App/App.css

      Para organizar os elementos, substitua o conteúdo pelo seguinte:

      prop-tutorial/src/components/App/App.css

      .wrapper {
          display: flex;
          flex-wrap: wrap;
          justify-content: space-between;
          padding: 20px;
      }
      
      .wrapper h1 {
          text-align: center;
          width: 100%;
      }
      

      Isso utilizará o flexbox para reorganizar os dados de modo que fiquem alinhados. O padding coloca um pouco de espaço na janela do navegador. O justify-content espalhará o espaço extra entre os elementos e o .wrapper h1 fornecerá a largura total do rótulo Animal.

      Salve e saia do arquivo. Quando fizer isso, o navegador será recarregado e você verá alguns dados espaçados.

      Projeto React no navegador com dados espaçados

      Adicionando props

      Com seus componentes configurados, adicione seu primeiro prop. Ao fazer o loop com os dados, você terá acesso a cada objeto na matriz data e a cada item que o objeto tem. Você adicionará cada um dos dados em um prop separado que você utilizará depois em seu componente AnimalCard.

      Abra App.js:

      • nano src/components/App/App.js

      Adicione um prop de name para AnimalCard.

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      ...
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {data.map(animal => (
              <AnimalCard
                key={animal.name}
                name={animal.name}
              />
            ))}
          </div>
        )
      }
      
      export default App;
      

      Salve e saia do arquivo. O prop name se parece com um atribuito HTML padrão, mas em vez de uma string, você passará a propriedade name do objeto animal entre chaves.

      Agora que você passou um prop para o novo componente, você precisa usá-lo. Abra o AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Todos os props que você passa para o componente são coletados em um objeto que será o primeiro argumento da função. Desestruture o objeto para extrair os props individuais:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      
      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard(props) {
        const { name } = props;
        return (
          <h2>{name}</h2>
        );
      }
      

      Observe que não é necessário desestruturar um prop para usá-lo. Esse é um método útil para lidar os com dados de exemplo neste tutorial.

      Após desestruturar o objeto, você pode usar os dados individuais. Neste caso, você utilizará o título em uma tag <h2>, colocando o valor entre chaves para que o React saiba avaliá-lo como JavaScript.

      Você também pode usar uma propriedade no objeto prop usando a notação de ponto. Por exemplo, crie um elemento <h2> assim: <h2>{props.title}</h2>. A vantagem da desestruturação é que você coleta os props não utilizados e usa o objeto operador rest.

      Salve e saia do arquivo. Quando fizer isso, o navegador será recarregado e você verá o nome específico para cada animal em vez de um espaço reservado.

      Projetos do React com nomes de animais renderizados

      A propriedade name é uma string, mas os props podem ser de qualquer tipo de dados que você poderia passar para uma função JavaScript. Para ver isto em funcionamento, adicione o resto dos dados.

      Abra o arquivo App.js:

      • nano src/components/App/App.js

      Adicione um prop para cada um dos seguintes: scientificName, size, diet e additional. Eles possuem strings, inteiros, matrizes e objetos.

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      ...
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {albums.map(album => (
              <AnimalCard
                additional={animal.additional}
                diet={animal.diet}
                key={animal.name}
                name={animal.name}
                scientificName={animal.scientificName}
                size={animal.size}
              />
            ))}
          </div>
        )
      }
      
      export default App;
      

      Como está criando um objeto, você pode adicioná-los em qualquer ordem que quiser. A ordem alfabética facilita a busca em uma lista de props, principalmente em uma lista muito grande. Você também pode colocá-los na mesma linha. No entanto, separá-los um por linha facilita a leitura.

      Salve e feche o arquivo. Abra o AnimalCard.js.

      • nano src/components/AnimalCard/AnimalCard.js

      Desta vez, desestruture os props na lista de parâmetros das funções e use os dados no componente:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard({
        additional,
        diet,
        name,
        scientificName,
        size
      }) {
        return (
          <div>
            <h2>{name}</h2>
            <h3>{scientificName}</h3>
            <h4>{size}kg</h4>
            <div>{diet.join(', ')}.</div>
          </div>
        );
      }
      

      Após retirar os dados, adicione o scientificName e o size em tags de cabeçalho. Será necessário converter a matriz em uma string para que o React possa exibi-la na página. Faça isso com o join(', '), que criará uma lista separada por vírgula.

      Salve e feche o arquivo. Quando fizer isso, o navegador recarregará e você verá os dados estruturados.

      Projeto React com os animais com os dados completos

      Você poderia criar uma lista parecida com o objeto additional, mas, em vez disso, adicione uma função para alertar o usuário com os dados. Isso dará a você a chance de passar funções como props e utilizar os dados dentro de um componente ao chamar uma função.

      Abra App.js:

      • nano src/components/App/App.js

      Crie uma função chamada showAdditionalData, que converte o objeto em uma string e a exibirá como um alerta.

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      ...
      
      function showAdditional(additional) {
        const alertInformation = Object.entries(additional)
          .map(information => `${information[0]}: ${information[1]}`)
          .join('n');
        alert(alertInformation)
      };
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {data.map(animal => (
              <AnimalCard
                additional={animal.additional}
                diet={animal.diet}
                key={animal.name}
                name={animal.name}
                scientificName={animal.scientificName}
                showAdditional={showAdditional}
                size={animal.size}
              />
            ))}
          </div>
        )
      }
      
      export default App;
      

      A função showAdditional converte o objeto para uma matriz de pares onde o primeiro item é a chave e o segundo é o valor. Em seguida, ele mapeia os dados convertendo o par de chaves para uma string. Depois, ele as junta com uma quebra de linha (n) antes de passar a string completa para a função de alerta.

      Como o JavaScript pode aceitar funções como argumentos, o React também aceita funções como props. Por este motivo, você pode passar o showAdditional para o AnimalCard como um prop chamado showAdditional.

      Salve e feche o arquivo. Abra o AnimalCard:

      • nano src/components/AnimalCard/AnimalCard.js

      Traga a função showAdditional do objeto props. Em seguida, crie um <button> com um evento onClick que chama a função com o objeto additional:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard({
        additional,
        diet,
        name,
        scientificName,
        showAdditional,
        size
      }) {
        return (
          <div>
            <h2>{name}</h2>
            <h3>{scientificName}</h3>
            <h4>{size}kg</h4>
            <div>{diet.join(', ')}.</div>
            <button onClick={() => showAdditional(additional)}>More Info</button>
          </div>
        );
      }
      

      Salve o arquivo. Quando fizer isso, o navegador será recarregado e você verá um botão após cada cartão. Quando você clicar no botão, receberá um alerta com os dados adicionais.

      Alerta com informações

      Se você tentar clicar em More Info para o Lion, receberá um erro. Isso acontece porque não existem dados adicionais para o leão. Você verá como corrigir isso no Passo 3.

      Finalmente, adicione um pouco de estilo ao cartão de música. Adicione um className de animal-wrapper ao div em AnimalCard:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
        return (
          <div className="animal-wrapper">
      ...
          </div>
        )
      }
      

      Salve e feche o arquivo. Abra o AnimalCard.css:

      • nano src/components/AnimalCard/AnimalCard.css

      Adicione o CSS para dar aos cartões e ao botão uma borda e um preenchimento:

      prop-tutorial/src/components/AnimalCard/AnimalCard.css

      .animal-wrapper {
          border: solid black 1px;
          margin: 10px;
          padding: 10px;
          width: 200px;
      }
      
      .animal-wrapper button {
          font-size: 1em;
          border: solid black 1px;
          padding: 10;
          background: none;
          cursor: pointer;
          margin: 10px 0;
      }
      

      Este CSS adicionará uma borda leve ao cartão e substituirá o estilo padrão do botão para um com borda e preenchimento. O cursor: pointer alterará o cursor quando você passar o mouse por cima do botão.

      Salve e feche o arquivo. Quando fizer isso, o navegador será recarregado e você verá os dados em cartões individuais.

      Projeto React com os cartões de animais com estilos aplicados

      Neste ponto, você criou dois componentes personalizados. Você passou dados para o segundo componente a partir do primeiro componente usando os props. Os props incluíram uma variedade de dados, como strings, números inteiros, matrizes, objetos e funções. Em seu segundo componente, você utilizou os props para criar um componente dinâmico usando o JSX.

      No próximo passo, você utilizará um sistema de tipos chamado de prop-types para especificar a estrutura que o componente espera ver. Este sistema criará uma previsibilidade em seu aplicativo e evitará bugs.

      Neste passo, você adicionará um sistema leve de tipos aos seus componentes com o PropTypes. O PropTypes age da mesma forma que outros sistemas de tipos, definindo explicitamente o tipo de dados que você espera receber de um certo prop. Ele também dá a você a chance de definir dados padronizados nos casos onde o prop não é muito necessário. Ao contrário da maioria dos sistemas de tipos, o PropTypes é um verificador do ambiente de execução. Desta forma, se os props não corresponderem ao tipo, o código ainda compilará, porém, ele exibirá um erro de console.

      Ao final deste passo, você adicionará a previsibilidade ao seu componente personalizado, definindo o tipo para cada prop. Isso garantirá que a próxima pessoa que trabalhar no componente tenha uma ideia clara sobre a estrutura dos dados de que o componente precisará.

      O pacote prop-types está incluído como parte da instalação do Create React App. Por este motivo, tudo que você precisa fazer é importá-lo em seu componente.

      Abra o AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Em seguida, importe o PropTypes do prop-types:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
      }
      

      Adicione o PropTypes diretamente à função do componente. Em JavaScript, as funções são objetos. Isso significa que você pode adicionar propriedades usando a sintaxe de ponto. Adicione os seguintes PropTypes para o AnimalCard.js:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
      }
      
      AnimalCard.propTypes = {
        additional: PropTypes.shape({
          link: PropTypes.string,
          notes: PropTypes.string
        }),
        diet: PropTypes.arrayOf(PropTypes.string).isRequired,
        name: PropTypes.string.isRequired,
        scientificName: PropTypes.string.isRequired,
        showAdditional: PropTypes.func.isRequired,
        size: PropTypes.number.isRequired,
      }
      

      Salve e feche o arquivo.

      Como você pode ver, existem vários tipos diferentes de PropTypes. Esta é apenas uma pequena amostra; consulte a documentação oficial do React para ver os outros PropTypes que você pode usar.

      Vamos começar pelo prop name. Aqui, você está especificando que name deve ser do tipo string. A propriedade scientificName é a mesma coisa. O size é um número que pode incluir floats como 1.5 e inteiros como 6. O showAdditional é uma função (func).

      O diet, por outro lado, é um pouco diferente. Neste caso, você está especificando que o diet será uma array, mas você também precisa especificar o que essa matriz conterá. Neste caso, a matriz conterá apenas strings. Se quiser misturar os tipos, utilize outro prop chamado de oneOfType, que recebe uma matriz de PropTypes válidos. Você pode utilizar o oneOfType em qualquer lugar. Se quiser que o size seja tanto um número quanto uma string, modifique-o para isto:

      size: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
      

      O prop additional também é um pouco mais complexo. Neste caso, você está especificando um objeto, mas, para ficar um pouco mais claro, você está declarando o que quer que o objeto contenha. Para fazer isso, utilize o PropTypes.shape, que recebe um objeto com campos adicionais que precisarão de seus próprios PropTypes. Neste caso, o link e o notes são ambos PropTypes.string.

      No momento, todos os dados estão bem estruturados e correspondem aos props. Para ver o que acontece se o PropTypes não for correspondido, abra seus dados:

      • nano src/components/App/data.js

      Modifique o tamanho para uma string no primeiro item:

      prop-tutorial/src/components/App/data.js

      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: '140',
          diet: ['meat'],
        },
      ...
      ]
      

      Salve o arquivo. Quando fizer isso, o navegador recarregará e você verá um erro no console.

      Error

      index.js:1 Warning: Failed prop type: Invalid prop `size` of type `string` supplied to `AnimalCard`, expected `number`. in AnimalCard (at App.js:18) in App (at src/index.js:9) in StrictMode (at src/index.js:8)

      Navegador com erro de tipo

      Ao contrário de outros sistemas de tipos como o TypeScript, o PropTypes não dará um aviso a você no momento da compilação. Enquanto não houver erros, ele ainda compilará. Isso significa que você pode publicar um código com erros de prop acidentalmente.

      Modifique os dados de volta para o tipo correto:

      prop-tutorial/src/components/App/data.js

      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: 140,
          diet: ['meat'],
        },
      ...
      ]
      

      Salve e feche o arquivo.

      Abra o AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Todo prop (com exceção do additional) possui a propriedade isRequired. Isso significa que eles são necessários. Se você não incluir um prop necessário, o código ainda compilará, mas você verá um erro do ambiente de execução no console.

      Se um prop não for necessário, você pode adicionar um valor padrão. Uma boa medida é sempre adicionar um valor padrão para evitar erros do ambiente de execução se um prop não for necessário. Por exemplo, no componente AnimalCard você está chamando uma função com os dados de additional. Se os dados não estiverem lá, a função tentará modificar um objeto que não existe e o aplicativo falhará.

      Para evitar este problema, adicione um defaultProp para o additional:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
      }
      
      AnimalCard.propTypes = {
        additional: PropTypes.shape({
          link: PropTypes.string,
          notes: PropTypes.string
        }),
      ...
      }
      
      AnimalCard.defaultProps = {
        additional: {
          notes: 'No Additional Information'
        }
      }
      

      Adicione o defaultProps à função usando a sintaxe de ponto da mesma forma que você fez com o propTypes. Em seguida, adicione um valor padrão que o componente deve usar se o prop for undefined. Neste caso, você está correspondendo o formato do additional incluindo uma mensagem de que não há informações adicionais.

      Salve e feche o arquivo. Quando fizer isso, o navegador recarregará. Após ele recarregar, clique no botão More Info para o Lion. Ele não possui um campo additional nos dados. Por este motivo, o prop está undefined. Porém, o AnimalCard será substituído no prop padrão.

      Navegador com a mensagem padrão no alerta

      Agora seus props estão bem documentados e são necessários ou possuem um padrão para garantir um código previsível. Isso ajudará que futuros desenvolvedores (incluindo você) entendam quais props de que um componente precisa. Ele facilitará a troca e a reutilização dos seus componentes, fornecendo informações completas sobre como o componente utilizará os dados que ele está recebendo.

      Conclusão

      Neste tutorial, você criou vários componentes que usam os props para exibir informações de um pai. Os props fornecem a flexibilidade que você precisa para começar a separar componentes maiores em partes menores e mais focadas. Agora que você não possui mais seus dados fortemente acoplados às informações de exibição, você pode fazer escolhas sobre como segmentar seu aplicativo.

      Os props são ferramentas importantes no desenvolvimento de aplicativos complexos. Eles dão a oportunidade de criar componentes que se adaptam aos dados que recebem. Com o PropTypes, você está criando componentes previsíveis e legíveis que darão a uma equipe a habilidade de reutilizar o trabalho uns dos outros para, desta maneira, criarem uma base de código flexível e estável. Se você quiser analisar mais tutoriais do React, dê uma olhada em nossa página sobre o React ou retorne à página da série Como Programar em React.js.



      Source link

      How To Create Wrapper Components in React with Props


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      In this tutorial, you’ll create wrapper components with props using the React JavaScript library. Wrapper components are components that surround unknown components and provide a default structure to display the child components. This pattern is useful for creating user interface (UI) elements that are used repeatedly throughout a design, like modals, template pages, and information tiles.

      To create wrapper components, you’ll first learn to use the rest and spread operators to collect unused props to pass down to nested components. Then you’ll create a component that uses the built-in children component to wrap nested components in JSX as if they were HTML elements. Finally, you’ll pass components as props to create flexible wrappers that can embed custom JSX in multiple locations in a component.

      During the tutorial, you’ll build components to display a list of animal data in the form of cards. You’ll learn to split data and refactor components as you create flexible wrapping components. By the end of this tutorial, you’ll have a working application that will use advanced prop techniques to create reusable components that will scale and adapt as you application grows and changes.

      Note: The first step sets up a blank project on which you will build the tutorial exercise. If you already have a working project and want to go directly to working with props, start with Step 2.

      Prerequisites

      Step 1 — Creating an Empty Project

      In this step, you’ll create a new project using Create React App. Then you will delete the sample project and related files that are installed when you bootstrap the project. Finally, you will create a simple file structure to organize your components. This will give you a solid basis on which to build this tutorial’s wrapper application in the next step.

      To start, make a new project. In your command line, run the following script to install a fresh project using create-react-app:

      • npx create-react-app wrapper-tutorial

      After the project is finished, change into the directory:

      In a new terminal tab or window, start the project using the Create React App start script. The browser will auto-refresh on changes, so leave this script running while you work:

      You will get a running local server. If the project did not open in a browser window, you can open it with http://localhost:3000/. If you are running this from a remote server, the address will be http://your_domain:3000.

      Your browser will load with a simple React application included as part of Create React App:

      React template project

      You will be building a completely new set of custom components, so you’ll need to start by clearing out some boilerplate code so that you can have an empty project.

      To start, open src/App.js in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js at How To Set Up a React Project with Create React App.

      Open src/App.js with the following command:

      You will see a file like this:

      wrapper-tutorial/src/App.js

      import React from 'react';
      import logo from './logo.svg';
      import './App.css';
      
      function App() {
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <p>
                Edit <code>src/App.js</code> and save to reload.
              </p>
              <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Learn React
              </a>
            </header>
          </div>
        );
      }
      
      export default App;
      

      Delete the line import logo from './logo.svg';. Then replace everything in the return statement to return a set of empty tags: <></>. This will give you a valid page that returns nothing. The final code will look like this:

      wrapper-tutorial/src/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        return <></>;
      }
      
      export default App;
      

      Save and exit the text editor.

      Finally, delete the logo. You won’t be using it in your application and you should remove unused files as you work. It will save you from confusion in the long run.

      In the terminal window type the following command:

      If you look at your browser, you will see a blank screen.

      blank screen in chrome

      Now that you have cleared out the sample Create React App project, create a simple file structure. This will help you keep your components isolated and independent.

      Create a directory called components in the src directory. This will hold all of you custom components.

      Each component will have its own directory to store the component file along with the styles, images if there are any, and tests.

      Create a directory for App:

      Move all of the App files into that directory. Use the wildcard, *, to select any files that start with App. regardless of file extension. Then use the mv command to put them into the new directory:

      • mv src/App.* src/components/App

      Next, update the relative import path in index.js, which is the root component that bootstraps the whole process:

      The import statement needs to point to the App.js file in the App directory, so make the following highlighted change:

      wrapper-tutorial/src/index.js

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      import App from './components/App/App';
      import * as serviceWorker from './serviceWorker';
      
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        document.getElementById('root')
      );
      
      // If you want your app to work offline and load faster, you can change
      // unregister() to register() below. Note this comes with some pitfalls.
      // Learn more about service workers: https://bit.ly/CRA-PWA
      serviceWorker.unregister();
      

      Save and exit the file.

      Now that the project is set up, you can create your first component.

      Step 2 — Collecting Unused Props with ...props

      In this step, you’ll create a component to display a set of data about a group of animals. Your component will contain a second nested component to display some information visually. To connect the parent and nested component, you’ll use the rest and spread operators to pass unused props from the parent to the child without the parent needing to be aware of the names or types of the props.

      By the end of this step, you’ll have a parent component that can provide props to nested components without having to know what the props are. This will keep the parent component flexible, allowing you to update the child component without having to change the parent.

      Creating an AnimalCard Component

      To start, create a set of data for your animals. First, open a file containing the data set in the components/App directory:

      • nano src/components/App/data.js

      Add the following data:

      src/components/App/data.js

      
      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: 140,
          diet: ['meat']
        },
        {
          name: 'Gorilla',
          scientificName: 'Gorilla beringei',
          size: 205,
          diet: ['plants', 'insects']
        },
        {
          name: 'Zebra',
          scientificName: 'Equus quagga',
          size: 322,
          diet: ['plants'],
        }
      ]
      

      This list of animals is an array of objects that includes the animal’s name, scientific name, weight, and diet.

      Save and close the file.

      Next, create a directory for the AnimalCard component:

      • mkdir src/components/AnimalCard

      Open a new file in the directo:

      • nano src/components/AnimalCard/AnimalCard.js

      Now add a component that will take the name, diet, and size as a prop and display it:

      wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      
      export default function AnimalCard({ diet, name, size }) {
        return(
          <div>
            <h3>{name}</h3>
            <div>{size}kg</div>
            <div>{diet.join(', ')}.</div>
          </div>
        )
      }
      
      AnimalCard.propTypes = {
        diet: PropTypes.arrayOf(PropTypes.string).isRequired,
        name: PropTypes.string.isRequired,
        size: PropTypes.number.isRequired,
      }
      

      Here you are destructuring the props in the parameter list for the AnimalCard function, then displaying the data in a div. The diet data is listed as a single string using the join() method. Each piece of data includes a corresponding PropType to make sure the data type is correct.

      Save and close the file.

      Now that you have your component and your data, you need to combine them together. To do that, import the component and the data into the root component of your project: App.js.

      First, open the component:

      • nano src/components/App/App.js

      From there, you can loop over the data and return a new AnimalCard with the relevant props. Add the highlighted lines to App.js:

      wrapper-tutorial/src/components/App/App.js

      import React from 'react';
      import './App.css';
      
      import animals from './data';
      import AnimalCard from '../AnimalCard/AnimalCard';
      
      function App() {
        return (
          <div className="wrapper">
            {animals.map(animal =>
              <AnimalCard
                diet={animal.diet}
                key={animal.name}
                name={animal.name}
                size={animal.size}
              />
            )}
          </div>
        );
      }
      
      export default App;
      

      Save and close the file.

      As you work on more complex projects, your data will come from more varied places, such as APIs, localStorage, or static files. But the process for using each of these will be similar: assign the data to a variable and loop over the data. In this case, the data is from a static file, so you are importing directly to a variable.

      In this code, you use the .map() method to iterate over animals and display the props. Notice that you do not have to use every piece of data. You are not explicitly passing the scientificName property, for example. You are also adding a separate key prop that React will use to keep track of the mapped data. Finally, you are wrapping the code with a div with a className of wrapper that you’ll use to add some styling.

      To add this styling, open App.css:

      • nano src/components/App/App.css

      Remove the boilerplate styling and add flex properties to a class called wrapper:

      prop-tutorial/src/components/App/App.js

      .wrapper {
          display: flex;
          flex-wrap: wrap;
          justify-content: space-between;
          padding: 20px;
      }
      

      This will use flexbox layout to organize the data so it will line up. padding gives some space in the browser window, and justify-content spreads out the extra space between elements.

      Save and exit the file. When you do, the browser will refresh and you’ll see some data spaced out.

      Browser with data spaced out

      Creating a Details Component

      You now have a simple component that displays the data. But let’s say you wanted to give the diet data a little flair by converting the text to an emoji. You can do this by converting the data in your component.

      React is designed to be flexible, so when you are thinking about how to convert data, you have a few different options:

      • You can create a function inside the component that converts the text to an emoji.
      • You can create a function and store it in a file outside the component so that you can reuse the logic across different components.
      • You can create a separate component that converts the text to an emoji.

      Each approach is fine when applied to the right use case, and you’ll find yourself switching between them as you build an application. To avoid premature abstraction and complexity, you should use the first option to start. If you find yourself wanting to reuse logic, you can pull the function out separately from the component. The third option is best if you want to have a reusable piece that includes the logic and the markup, or that you want to isolate to use across the application.

      In this case, we’ll make a new component, since we will want to add more data later and we are combining markup with conversion logic.

      The new component will be called AnimalDetails. To make it, create a new directory:

      • mkdir src/components/AnimalDetails

      Next, open AnimalDetails.js in your text editor:

      • nano src/components/AnimalDetails/AnimalDetails.js

      Inside the file, make a small component that displays the diet as an emoji:

      wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalDetails.css';
      
      function convertFood(food) {
        switch(food) {
          case 'insects':
            return '🐜';
          case 'meat':
            return '🍖';
          case 'plants':
          default:
            return '🌱';
        }
      }
      
      export default function AnimalDetails({ diet }) {
        return(
          <div className="details">
            <h4>Details:</h4>
            <div>
              Diet: {diet.map(food => convertFood(food)).join(' ')}
            </div>
          </div>
        )
      }
      
      AnimalDetails.propTypes = {
        diet: PropTypes.arrayOf(PropTypes.string).isRequired,
      }
      

      The AnimalDetails.propTypes object sets up the function to take a prop of diet that is an array of strings. Then inside the component, the code loops over the diet and converts the string to an emoji using the switch statement.

      Save and close the file.

      You are also importing some CSS, so let’s add that now.

      Open AnimalDetails.css:

      • nano src/components/AnimalDetails/AnimalDetails.css

      Add some CSS to give the element a border and margin to separate the details from the rest of the component:

      wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.css

      .details {
          border-top: gray solid 1px;
          margin: 20px 0;
      }
      

      We use .details to match the rule to elements with a className of details.

      Save and close the file.

      Now that you have a new custom component, you can add it to your AnimalCard component. Open AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Replace the diet.join statement with the new AnimalDetails component and pass diet as a prop by adding the highlighted lines:

      wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import AnimalDetails from '../AnimalDetails/AnimalDetails';
      
      export default function AnimalCard({ diet, name, size }) {
        return(
          <div>
            <h3>{name}</h3>
            <div>{size}kg</div>
            <AnimalDetails
              diet={diet}
            />
          </div>
        )
      }
      
      AnimalCard.propTypes = {
        diet: PropTypes.arrayOf(PropTypes.string).isRequired,
        name: PropTypes.string.isRequired,
        size: PropTypes.number.isRequired,
      }
      

      Save the file and you’ll see the new details in the browser.

      Browser with details

      Passing Details Through a Component with ...props

      The components are working well together, but there’s a slight inefficiency in AnimalCard. You are explicitly pulling diet out from the props argument, but you aren’t using the data. Instead, you are passing it through to the component. There’s nothing inherently wrong about this—in fact, it’s often better to err on the side of too much communication. But in doing this, you make your code more difficult to maintain. Whenever you want to pass new data to AnimalDetails, you need to update three places: App, where you pass the props, AnimalDetails, which consumes the prop, and AnimalCard, which is the go-between.

      A better way is to gather any unused props inside AnimalCard and then pass those directly to AnimalDetails. This gives you the chance to make changes to AnimalDetails without changing AnimalCard. In effect, AnimalCard doesn’t need to know anything about the props or the PropTypes that are going into AnimalDetails.

      To do that, you’ll use the object rest operator. This operator collects any items that are not pulled out during destructuring and saves them into a new object.

      Here’s a simple example:

      const dog = {
          name: 'dog',
          diet: ['meat']
      }
      
      const { name, ...props  } = dog;
      

      In this case, the variable name will be 'dog' and the variable props will be { diet: ['meat']}.

      Up till now, you’ve passed all props as if they were HTML attributes, but you can also use objects to send props. To use an object as a prop, you need to use the spread operator—...props—surrounded with curly braces. This will change each key-value pair into a prop.

      Open AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Inside, remove diet from the destructured object and instead collect the rest of the props into a variable called props. Then pass those props directly to AnimalDetails:

      wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import AnimalDetails from '../AnimalDetails/AnimalDetails';
      
      export default function AnimalCard({ name, size, ...props }) {
        return(
          <div>
            <h3>{name}</h3>
            <div>{size}kg</div>
            <AnimalDetails
              {...props}
            />
          </div>
        )
      }
      
      AnimalCard.propTypes = {
        name: PropTypes.string.isRequired,
        size: PropTypes.number.isRequired,
      }
      

      Notice that you can remove the diet PropType since you are not using the prop in this component.

      In this case, you are only passing one prop to AnimalDetails. In cases where you have multiple props, the order will matter. A later prop will overwrite earlier props, so if you have a prop you want to take priority, make sure it is last. This can cause some confusion if your props object has a property that is also a named value.

      Save and close the file. The browser will refresh and everything will look the same:

      Browser with details

      To see how the ...props object adds flexibility, let’s pass the scientificName to AnimalDetails via the AnimalCard component.

      First, open App.js:

      • nano src/components/App/App.js

      Then pass the scientificName as a prop:

      wrapper-tutorial/src/components/App/App.js

      import React from 'react';
      import './App.css';
      
      import animals from './data';
      import AnimalCard from '../AnimalCard/AnimalCard';
      
      function App() {
        return (
          <div className="wrapper">
            {animals.map(animal =>
              <AnimalCard
                diet={animal.diet}
                key={animal.name}
                name={animal.name}
                size={animal.size}
                scientificName={animal.scientificName}
              />
            )}
          </div>
        );
      }
      
      export default App;
      

      Save and close the file.

      Skip over AnimalCard; you won’t need to make any changes there. Then open AnimalDetails so you can consume the new prop:

      • nano src/components/AnimalDetails/AnimalDetails.js

      The new prop will be a string, which you’ll add to the details list along with a line declaring the PropType:

      wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js

      import React from 'react';
      ...
      export default function AnimalDetails({ diet, scientificName }) {
        return(
          <div className="details">
            <h4>Details:</h4>
            <div>
              Scientific Name: {scientificName}.
            </div>
            <div>
              Diet: {diet.map(food => convertFood(food)).join(' ')}
            </div>
          </div>
        )
      }
      
      AnimalDetails.propTypes = {
        diet: PropTypes.arrayOf(PropTypes.string).isRequired,
        scientificName: PropTypes.string.isRequired,
      }
      

      Save and close the file. When you do, the browser will refresh and you’ll see the new details without any changes to the AnimalCard component:

      Browser with scientific name

      In this step, you learned how to create flexible parent props that can take unknown props and pass them into nested components with the spread operator. This is a common pattern that will give you the flexibility you need to create components with focused responsibilities. In the next step, you’ll create components that can take unknown components as a prop using the built in children prop.

      Step 3 — Creating Wrapper Components with children

      In this step, you’ll create a wrapper component that can take an unknown group of components as a prop. This will give you the ability to nest components like standard HTML, and it will give you a pattern for creating reusable wrappers that will let you make a variety of components that need a common design but a flexible interior.

      React gives you a built-in prop called children that collects any children components. Using this makes creating wrapper components intuitivie and readable.

      To start, make a new component called Card. This will be a wrapper component to create a standard style for any new card components.

      Create a new directory:

      • mkdir src/components/Card

      Then open the Card component in your text editor:

      • nano src/components/Card/Card.js

      Create a component that takes children and title as props and wraps them in a div by adding the following code:

      wrapper-tutorial/src/components/Card/Card.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './Card.css';
      
      export default function Card({ children, title }) {
        return(
          <div className="card">
            <div className="card-details">
              <h2>{title}</h2>
            </div>
            {children}
          </div>
        )
      }
      
      Card.propTypes = {
        children: PropTypes.oneOfType([
          PropTypes.arrayOf(PropTypes.element), 
          PropTypes.element.isRequired
        ]),
        title: PropTypes.string.isRequired,
      }
      

      The PropTypes for the children are new. The children prop can either be a JSX element or an array of JSX elements. The title is a string.

      Save and close the file.

      Next, add some styling. Open Card.css:

      • nano src/components/Card/Card.css

      Your card will have a border and a line under the details.

      wrapper-tutorial/src/components/Card/Card.css

      .card {
          border: black solid 1px;
          margin: 10px;
          padding: 10px;
          width: 200px;
      }
      
      .card-details {
          border-bottom: gray solid 1px;
          margin-bottom: 20px;
      }
      

      Save and close the file. Now that you have your component you need to use it. You could wrap each AnimalCard with the Card component in App.js, but since the name AnimalCard implies it is already a Card, it would be better to use the Card component inside of AnimalCard.

      Open up AnimalCard:

      • nano src/components/AnimalCard/AnimalCard.js

      Unlike other props, you don’t pass children explicitly. Instead, you include the JSX as if they were HTML child elements. In other words, you just nest them inside of the element, like the following:

      wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import Card from '../Card/Card';
      import AnimalDetails from '../AnimalDetails/AnimalDetails';
      
      export default function AnimalCard({ name, size, ...props }) {
        return(
          <Card title="Animal">
            <h3>{name}</h3>
            <div>{size}kg</div>
            <AnimalDetails
              {...props}
            />
          </Card>
        )
      }
      
      AnimalCard.propTypes = {
        name: PropTypes.string.isRequired,
        size: PropTypes.number.isRequired,
      }
      

      Unlike a React component, you do not need to have a single root element as a child. That’s why the PropType for Card specified it could be an array of elements or a single element. In addition to passing the children as nested components, you are giving the card a title of Animal.

      Save and close the file. When you do, the browser will refresh and you’ll see the updated card component.

      Browser with cards

      Now you have a reusable Card component that can take any number of nested children. The primary advantage of this is that you can reuse the Card with any arbitrary component. If you wanted to make a Plant card, you could do that by wrapping the plant information with the Card component. It doesn’t even need to relate at all: If you wanted to reuse the Card component in a completely different applications that lists things like music or account data, you could do that, too. The Card component doesn’t care what the children are; you are just reusing the wrapper element, which in this case is the styled border and title.

      The downside to using children is that you can only have one instance of the child prop. Occasionally, you’ll want a component to have custom JSX in multiple places. Fortunately, you can do that by passing JSX and React components as props, which we will cover in the next step.

      Step 4 — Passing Components as Props

      In this step, you’ll modify your Card component to take other components as props. This will give your component maximum flexibility to display unknown components or JSX in multiple locations throughout the page. Unlike children, which you can only use once, you can have as many components as props, giving your wrapper component the ability to adapt to a variety of needs while maintaining a standard look and structure.

      By the end of this step, you’ll have a component that can wrap children components and also display other components in the card. This pattern will give you flexibility when you need to create components that need information that is more complex than simple strings and integers.

      Let’s modify the Card component to take an arbitrary React element called details.

      First, open the Card component:

      • nano src/components/Card/Card.js

      Next, add a new prop called details and place it below the <h2> element:

      wrapper-tutorial/src/components/Card/Card.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './Card.css';
      
      export default function Card({ children, details, title }) {
        return(
          <div className="card">
            <div className="card-details">
              <h2>{title}</h2>
              {details}
            </div>
            {children}
          </div>
        )
      }
      
      Card.propTypes = {
        children: PropTypes.oneOfType([
          PropTypes.arrayOf(PropTypes.element), 
          PropTypes.element.isRequired
        ]),
        details: PropTypes.element,
        title: PropTypes.string.isRequired,
      }
      
      Card.defaultProps = {
        details: null,
      }
      

      This prop will have the same type as children, but it should be optional. To make it optional, you add a default value of null. In this case, if a user passes no details, the component will still be valid and will not display anything extra.

      Save and close the file. The page will refresh and you’ll see the same image as before:

      Browser with cards

      Now add some details to the AnimalCard. First, open AnimalCard.

      • nano src/components/AnimalCard/AnimalCard.js

      Since the Card component is already using children, you’ll need to pass the new JSX component as a prop. Since these are all mammals, add that to the card, but wrap it in <em> tags to make it italic.

      wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      ...
      
      export default function AnimalCard({ name, size, ...props }) {
        return(
          <Card title="Animal" details={<em>Mammal</em>}>
            <h3>{name}</h3>
            <div>{size}kg</div>
            <AnimalDetails
              {...props}
            />
          </Card>
        )
      }
      ...
      

      Save the file. When you do, the browser will refresh and you’ll see the update, including the phrase Mammal.

      Browser with card and details

      This prop is already powerful because it can take JSX of any size. In this example, you added only a single element, but you could pass as much JSX as you wanted. It also doesn’t have to be JSX. If you have a complicated markup for example, you wouldn’t want to pass it directly in the prop; this would be difficult to read. Instead, you could create a separate component and then pass the component as a prop.

      To see this at work, pass AnimalDetails to the details prop:

      wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      ...
      
      export default function AnimalCard({ name, size, ...props }) {
        return(
          <Card
            title="Animal"
            details={
              <AnimalDetails
                {...props}
              />
            }
          >
            <h3>{name}</h3>
            <div>{size}kg</div>
          </Card>
        )
      }
      ...
      

      AnimalDetails is more complicated and has a number of lines of markup. If you were to add it directly to details, it would increase the prop substantially and make it difficult to read.

      Save and close the file. When you do, the browser will refresh and the details will appear at the top of the card.

      Card with details at the top

      Now you have a Card component that can take custom JSX and place it in multiple spots. You are not restricted to a single prop; you can pass elements to as many props as you want. This gives you the ability to create flexible wrapping components that can give other developers the opportunity to customize a component while retaining its overall style and functionality.

      Passing a component as a prop isn’t perfect. It’s a little more difficult to read and isn’t as clear as passing children, but they are just as flexible and you can use as many of them as you want in a component. You should use children first, but don’t hesitate to fall back to props if that is not enough.

      In this step, you learned how to pass JSX and React components as props to another component. This will give your component the flexibility to handle many situations where a wrapper component may need multiple props to handle JSX or components.

      Conclusion

      You have created a variety of wrapping components that can display data flexibly while keeping a predictable look and structure. You created components that can collect and pass unknown props to nested components. You also used the built-in children prop to create wrapper components that can handle an arbitrary number of nested elements. Finally, you created a component that can take JSX or React components as a prop so that your wrapper component can handle multiple instances of different customizations.

      Wrapper components give you the ability to adapt to unknown circumstances while also maximizing code reuse and consistency. This pattern is useful for creating basic UI elements that you will reuse throughout an application including: buttons, alerts, modals, slide shows, and more. You’ll find yourself returning to it many times.

      If you would like to look at more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link

      How To Customize React Components with Props


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      In this tutorial, you’ll create custom components by passing props to your component. Props are arguments that you provide to a JSX element. They look like standard HTML props, but they aren’t predefined and can have many different JavaScript data types including numbers, strings, functions, arrays, and even other React components. Your custom components can use props to display data or use the data to make the components interactive. Props are a key part of creating components that are adaptable to different situations, and learning about them will give you the tools to develop custom components that can handle unique situations.

      After adding props to your component, you will use PropTypes to define the type of data you expect a component to receive. PropTypes are a simple type system to check that data matches the expected types during runtime. They serve as both documentation and an error checker that will help keep your application predictable as it scales.

      By the end of the tutorial, you’ll use a variety of props to build a small application that will take an array of animal data and display the information, including the name, scientific name, size, diet, and additional information.

      Note: The first step sets up a blank project on which you will build the tutorial exercise. If you already have a working project and want to go directly to working with props, start with Step 2.

      Prerequisites

      Step 1 — Creating an Empty Project

      In this step, you’ll create a new project using Create React App. Then you will delete the sample project and related files that are installed when you bootstrap the project. Finally, you will create a simple file structure to organize your components.

      To start, make a new project. In your command line, run the following script to install a fresh project using create-react-app:

      • npx create-react-app prop-tutorial

      After the project is finished, change into the directory:

      In a new terminal tab or window, start the project using the Create React App start script. The browser will autorefresh on changes, so leave this script running the whole time that you work:

      You will get a running local server. If the project did not open in a browser window, you can open it by navigating to http://localhost:3000/. If you are running this from a remote server, the address will be http://your_domain:3000.

      Your browser will load with a simple React application included as part of Create React App:

      React template project

      You will be building a completely new set of custom components. You’ll start by clearing out some boilerplate code so that you can have an empty project.

      To start, open src/App.js in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js at How To Set Up a React Project with Create React App.

      Open src/App.js with the following command:

      You will see a file like this:

      prop-tutorial/src/App.js

      import React from 'react';
      import logo from './logo.svg';
      import './App.css';
      
      function App() {
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <p>
                Edit <code>src/App.js</code> and save to reload.
              </p>
              <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Learn React
              </a>
            </header>
          </div>
        );
      }
      
      export default App;
      

      Delete the line import logo from './logo.svg';. Then replace everything in the return statement to return a set of empty tags: <></>. This will give you a validate page that returns nothing. The final code will look like this:

      prop-tutorial/src/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        return <></>;
      }
      
      export default App;
      

      Save and exit the text editor.

      Finally, delete the logo. You won’t be using it in your application and you should remove unused files as you work. It will save you from confusion in the future.

      In the terminal window type the following command:

      If you look at your browser, you will see a blank screen.

      blank screen in chrome

      Now that you have cleared out the sample Create React App project, create a simple file structure. This will help you keep your components isolated and independent.

      Create a directory called components in the src directory. This will hold all of your custom components.

      Each component will have its own directory to store the component file along with the styles, images if there are any, and tests.

      Create a directory for App:

      Move all of the App files into that directory. Use the wildcard, *, to select any files that start with App. regardless of file extension. Then use the mv command to put them into the new directory.

      • mv src/App.* src/components/App

      Finally, update the relative import path in index.js, which is the root component that bootstraps the whole process.

      The import statement needs to point to the App.js file in the App directory, so make the following highlighted change:

      prop-tutorial/src/index.js

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      import App from './components/App/App';
      import * as serviceWorker from './serviceWorker';
      
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        document.getElementById('root')
      );
      
      // If you want your app to work offline and load faster, you can change
      // unregister() to register() below. Note this comes with some pitfalls.
      // Learn more about service workers: https://bit.ly/CRA-PWA
      serviceWorker.unregister();
      

      Save and exit the file.

      Now that the project is set up, you can create your first component.

      Step 2 — Building Dynamic Components with Props

      In this step, you will create a component that will change based on the input information called props. Props are the arguments you pass to a function or class, but since your components are transformed into HTML-like objects with JSX, you will pass the props like they are HTML attributes. Unlike HTML elements, you can pass many different data types, from strings, to arrays, to objects, and even functions.

      Here you will create a component that will display information about animals. This component will take the name and scientific name of the animal as strings, the size as an integer, the diet as an array of strings, and additional information as an object. You’ll pass the information to the new component as props and consume that information in your component.

      By the end of this step, you’ll have a custom component that will consume different props. You’ll also reuse the component to display an array of data using a common component.

      Adding Data

      First, you need some sample data. Create a file in the src/App directory called data.

      • touch src/components/App/data.js

      Open the new file in your text editor:

      • nano src/components/App/data.js

      Next, add an array of objects you will use as sample data:

      prop-tutorial/src/components/App/data.js

      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: 140,
          diet: ['meat'],
        },
        {
          name: 'Gorilla',
          scientificName: 'Gorilla beringei',
          size: 205,
          diet: ['plants', 'insects'],
          additional: {
            notes: 'This is the eastern gorilla. There is also a western gorilla that is a different species.'
          }
        },
        {
          name: 'Zebra',
          scientificName: 'Equus quagga',
          size: 322,
          diet: ['plants'],
          additional: {
            notes: 'There are three different species of zebra.',
            link: 'https://en.wikipedia.org/wiki/Zebra'
          }
        }
      ]
      

      The array of objects contains a variety of data and will give you an opportunity to try a variety of props. Each object is a separate animal with the name of the animal, the scientific name, size, diet, and an optional field called additional, which will contain links or notes. In this code, you also exported the array as the default.

      Save and exit the file.

      Creating Components

      Next, create a placeholder component called AnimalCard. This component will eventually take props and display the data.

      First, make a directory in src/components called AnimalCard then touch a file called src/components/AnimalCard/AnimalCard.js and a CSS file called src/components/AnimalCard/AnimalCard.css.

      • mkdir src/components/AnimalCard
      • touch src/components/AnimalCard/AnimalCard.js
      • touch src/components/AnimalCard/AnimalCard.css

      Open AnimalCard.js in your text editor:

      • nano src/components/AnimalCard/AnimalCard.js

      Add a basic component that imports the CSS and returns an <h2> tag.

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard() {
        return <h2>Animal</h2>
      }
      

      Save and exit the file. Now you need to import the data and component into your base App component.

      Open src/components/App/App.js:

      • nano src/components/App/App.js

      Import the data and the component, then loop over the data returning the component for each item in the array:

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      import data from './data';
      import AnimalCard from '../AnimalCard/AnimalCard';
      import './App.css';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {data.map(animal => (
              <AnimalCard key={animal.name}/>
            ))}
          </div>
        )
      }
      
      export default App;
      

      Save and exit the file. Here, you use the .map() array method to iterate over the data. In addition to adding this loop, you also have a wrapping div with a class that you will use for styling and an <h1> tag to label your project.

      When you save, the browser will reload and you’ll see a label for each card.

      React project in the browser without styling

      Next, add some styling to line up the items. Open App.css:

      • nano src/components/App/App.css

      Replace the contents with the following to arrange the elements:

      prop-tutorial/src/components/App/App.css

      .wrapper {
          display: flex;
          flex-wrap: wrap;
          justify-content: space-between;
          padding: 20px;
      }
      
      .wrapper h1 {
          text-align: center;
          width: 100%;
      }
      

      This will use flexbox to rearrange the data so it will line up. The padding gives some space in the browser window. justify-content will spread out the extra space between elements, and .wrapper h1 will give the Animal label the full width.

      Save and exit the file. When you do, the browser will refresh and you’ll see some data spaced out.

      React project in the browser with data spaced out

      Adding Props

      Now that you have your components set up, you can add your first prop. When you looped over your data, you had access to each object in the data array and the items it contained. You will add each piece of the data to a separate prop that you will then use in your AnimalCard component.

      Open App.js:

      • nano src/components/App/App.js

      Add a prop of name to AnimalCard.

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      ...
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {data.map(animal => (
              <AnimalCard
                key={animal.name}
                name={animal.name}
              />
            ))}
          </div>
        )
      }
      
      export default App;
      

      Save and exit the file. The name prop looks like a standard HTML attribute, but instead of a string, you’ll pass the name property from the animal object in curly braces.

      Now that you’ve passed one prop to the new component, you need to use it. Open AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      All props that you pass into the component are collected into an object that will be the first argument of your function. Destructure the object to pull out individual props:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      
      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard(props) {
        const { name } = props;
        return (
          <h2>{name}</h2>
        );
      }
      

      Note that you do not need to destructure a prop to use it, but that this is a useful method for dealing with the sample data in this tutorial.

      After you destructure the object, you can use the individual pieces of data. In this case, you’ll use the title in an <h2> tag, surrounding the value with curly braces so that React will know to evaluate it as JavaScript.

      You can also use a property on the prop object using dot notation. As an example, you could create an <h2> element like this: <h2>{props.title}</h2>. The advantage of destructring is that you can collect unused props and use the object rest operator.

      Save and exit the file. When you do, the browser will reload and you’ll see the specific name for each animal instead of a placeholder.

      React projects with animal names rendered

      The name property is a string, but props can be any data type that you could pass to a JavaScript function. To see this at work, add the rest of the data.

      Open the App.js file:

      • nano src/components/App/App.js

      Add a prop for each of the following: scientificName, size, diet, and additional. These include strings, integers, arrays, and objects.

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      ...
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {albums.map(album => (
              <AnimalCard
                additional={animal.additional}
                diet={animal.diet}
                key={animal.name}
                name={animal.name}
                scientificName={animal.scientificName}
                size={animal.size}
              />
            ))}
          </div>
        )
      }
      
      export default App;
      

      Since you are creating an object, you can add them in any order you want. Alphabetizing makes it easier to skim a list of props especially in a larger list. You also can add them on the same line, but separating to one per line keeps things readable.

      Save and close the file. Open AnimalCard.js.

      • nano src/components/AnimalCard/AnimalCard.js

      This time, destructure the props in the function parameter list and use the data in the component:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard({
        additional,
        diet,
        name,
        scientificName,
        size
      }) {
        return (
          <div>
            <h2>{name}</h2>
            <h3>{scientificName}</h3>
            <h4>{size}kg</h4>
            <div>{diet.join(', ')}.</div>
          </div>
        );
      }
      

      After pulling out the data, you can add the scientificName and size into heading tags, but you’ll need to convert the array into a string so that React can display it on the page. You can do that with join(', '), which will create a comma separated list.

      Save and close the file. When you do, the browser will refresh and you’ll see the structured data.

      React project with animals with full data

      You could create a similar list with the additional object, but instead add a function to alert the user with the data. This will give you the chance to pass functions as props and then use data inside a component when you call a function.

      Open App.js:

      • nano src/components/App/App.js

      Create a function called showAdditionalData that will convert the object to a string and display it as an alert.

      prop-tutorial/src/components/App/App.js

      import React from 'react';
      ...
      
      function showAdditional(additional) {
        const alertInformation = Object.entries(additional)
          .map(information => `${information[0]}: ${information[1]}`)
          .join('n');
        alert(alertInformation)
      };
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Animals</h1>
            {data.map(animal => (
              <AnimalCard
                additional={animal.additional}
                diet={animal.diet}
                key={animal.name}
                name={animal.name}
                scientificName={animal.scientificName}
                showAdditional={showAdditional}
                size={animal.size}
              />
            ))}
          </div>
        )
      }
      
      export default App;
      

      The function showAdditional converts the object to an array of pairs where the first item is the key and the second is the value. It then maps over the data converting the key-pair to a string. Then it joins them with a line break—n—before passing the complete string to the alert function.

      Since JavaScript can accept functions as arguments, React can also accept functions as props. You can therefore pass showAdditional to AnimalCard as a prop called showAdditional.

      Save and close the file. Open AnimalCard:

      • nano src/components/AnimalCard/AnimalCard.js

      Pull the showAdditional function from the props object, then create a <button> with an onClick event that calls the function with the additional object:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard({
        additional,
        diet,
        name,
        scientificName,
        showAdditional,
        size
      }) {
        return (
          <div>
            <h2>{name}</h2>
            <h3>{scientificName}</h3>
            <h4>{size}kg</h4>
            <div>{diet.join(', ')}.</div>
            <button onClick={() => showAdditional(additional)}>More Info</button>
          </div>
        );
      }
      

      Save the file. When you do, the browser will refresh and you’ll see a button after each card. When you click the button, you’ll get an alert with the additional data.

      Alert with information

      If you try clicking More Info for the Lion, you will get an error. That’s because there is no additional data for the lion. You’ll see how to fix that in Step 3.

      Finally, add some styling to the music card. Add a className of animal-wrapper to the div in AnimalCard:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
        return (
          <div className="animal-wrapper">
      ...
          </div>
        )
      }
      

      Save and close the file. Open AnimalCard.css:

      • nano src/components/AnimalCard/AnimalCard.css

      Add CSS to give the cards and the button a small border and padding:

      prop-tutorial/src/components/AnimalCard/AnimalCard.css

      .animal-wrapper {
          border: solid black 1px;
          margin: 10px;
          padding: 10px;
          width: 200px;
      }
      
      .animal-wrapper button {
          font-size: 1em;
          border: solid black 1px;
          padding: 10;
          background: none;
          cursor: pointer;
          margin: 10px 0;
      }
      

      This CSS will add a slight border to the card and replace the default button styling with a border and padding. cursor: pointer will change the cursor when you hover over the button.

      Save and close the file. When you do the browser will refresh and you’ll see the data in individual cards.

      React project with styled animal cards

      At this point, you’ve created two custom components. You’ve passed data to the second component from the first component using props. The props included a variety of data, such as strings, integers, arrays, objects, and functions. In your second component, you used the props to create a dynamic component using JSX.

      In the next step, you’ll use a type system called prop-types to specify the structure your component expects to see, which will create predictability in your app and prevent bugs.

      Step 3 — Creating Predictable Props with PropTypes and defaultProps

      In this step, you’ll add a light type system to your components with PropTypes. PropTypes act like other type systems by explicitly defining the type of data you expect to receive for a certain prop. They also give you the chance to define default data in cases where the prop is not always required. Unlike most type systems, PropTypes is a runtime check, so if the props do not match the type the code will still compile, but will also display a console error.

      By the end of this step, you’ll add predictability to your custom component by defining the type for each prop. This will ensure that the next person to work on the component will have a clear idea of the structure of the data the component will need.

      The prop-types package is included as part of the Create React App installation, so to use it, all you have to do is import it into your component.

      Open up AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Then import PropTypes from prop-types:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
      }
      

      Add PropTypes directly to the component function. In JavaScript, functions are objects, which means you can add properties using dot syntax. Add the following PropTypes to AnimalCard.js:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
      }
      
      AnimalCard.propTypes = {
        additional: PropTypes.shape({
          link: PropTypes.string,
          notes: PropTypes.string
        }),
        diet: PropTypes.arrayOf(PropTypes.string).isRequired,
        name: PropTypes.string.isRequired,
        scientificName: PropTypes.string.isRequired,
        showAdditional: PropTypes.func.isRequired,
        size: PropTypes.number.isRequired,
      }
      

      Save and close the file.

      As you can see, there are many different PropTypes. This is only a small sample; see the official React documentation to see the others you can use.

      Let’s start with the name prop. Here, you are specifying that name must be a string. The property scientificName is the same. size is a number, which can include both floats such as 1.5 and integers such as 6. showAdditional is a function (func).

      diet, on the other hand, is a little different. In this case, you are specifying that diet will be an array, but you also need to specify what this array will contain. In this case, the array will only contain strings. If you want to mix types, you can use another prop called oneOfType, which takes an array of valid PropTypes. You can use oneOfType anywhere, so if you wanted size to be either a number or a string you could change it to this:

      size: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
      

      The prop additional is also a little more complex. In this case, you are specifying an object, but to be a little more clear, you are stating what you want the object to contain. To do that, you use PropTypes.shape, which takes an object with additional fields that will need their own PropTypes. In this case, link and notes are both PropTypes.string.

      Currently, all of the data is well-formed and matches the props. To see what happens if the PropTypes don’t match, open up your data:

      • nano src/components/App/data.js

      Change the size to a string on the first item:

      prop-tutorial/src/components/App/data.js

      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: '140',
          diet: ['meat'],
        },
      ...
      ]
      

      Save the file. When you do the browser will refresh and you’ll see an error in the console.

      Error

      index.js:1 Warning: Failed prop type: Invalid prop `size` of type `string` supplied to `AnimalCard`, expected `number`. in AnimalCard (at App.js:18) in App (at src/index.js:9) in StrictMode (at src/index.js:8)

      Browser with type error

      Unlike other type systems such as TypeScript, PropTypes will not give you a warning at build time, and as long as there are no code errors, it will still compile. This means that you could accidentally publish code with prop errors.

      Change the data back to the correct type:

      prop-tutorial/src/components/App/data.js

      export default [
        {
          name: 'Lion',
          scientificName: 'Panthero leo',
          size: 140,
          diet: ['meat'],
        },
      ...
      ]
      

      Save and close the file.

      Open up AnimalCard.js:

      • nano src/components/AnimalCard/AnimalCard.js

      Every prop except for additional has the isRequired property. That means, that they are required. If you don’t include a required prop, the code will still compile, but you’ll see a runtime error in the console.

      If a prop isn’t required, you can add a default value. It’s good practice to always add a default value to prevent runtime errors if a prop is not required. For example, in the AnimalCard component, you are calling a function with the additional data. If it’s not there, the function will try and modify an object that doesn’t exist and the application will crash.

      To prevent this problem, add a defaultProp for additional:

      prop-tutorial/src/components/AnimalCard/AnimalCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import './AnimalCard.css'
      
      export default function AnimalCard({
      ...
      }
      
      AnimalCard.propTypes = {
        additional: PropTypes.shape({
          link: PropTypes.string,
          notes: PropTypes.string
        }),
      ...
      }
      
      AnimalCard.defaultProps = {
        additional: {
          notes: 'No Additional Information'
        }
      }
      

      You add the defaultProps to the function using dot syntax just as you did with propTypes, then you add a default value that the component should use if the prop is undefined. In this case, you are matching the shape of additional, including a message that the there is no additional information.

      Save and close the file. When you do, the browser will refresh. After it refreshes, click on the More Info button for Lion. It has no additional field in the data so the prop is undefined. But AnimalCard will substitute in the default prop.

      Browser with default message in the alert

      Now your props are well-documented and are either required or have a default to ensure predictable code. This will help future developers (including yourself) understand what props a component needs. It will make it easier to swap and reuse your components by giving you full information about how the component will use the data it is receiving.

      Conclusion

      In this tutorial, you have created several components that use props to display information from a parent. Props give you the flexibility to begin to break larger components into smaller, more focused pieces. Now that you no longer have your data tightly coupled with your display information, you have the ability to make choices about how to segment your application.

      Props are a crucial tool in building complex applications, giving the opportunity to create components that can adapt to the data they receive. With PropTypes, you are creating predictable and readable components that will give a team the ability to reuse each other’s work to create a flexible and stable code base. If you would like to look at more React tutorials, take a look at our React Topic page, or return to the How To Code in React.js series page.



      Source link