Text nodes cannot appear as a child of <table> [ReactJS]

Viewed 509

Why am I receiving this warning?

Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <table>.

In some cases I saw that it is about some white spaces, but I don't see how this applies in here.

My code:

        return (
            <div className="container">
                <div>
                    <h2 style={{color: 'red'}}>Lista de Clientes</h2>
                </div>
                <br/>

                <table className="table table-bordered">
                    <thead className="thead-dark">
                        <tr>
                            <th>#</th>
                            <th>Nome</th>
                            <th>Telefone</th>
                            <th>E-mail</th>
                            <th>Detalhar</th>
                            <th>Excluir</th>
                        </tr>
                    </thead>
                    { inserirClientes }
                </table>
            </div>
        );

In here the loop that creates inserirClientes

        let inserirClientes = this.state.mensagem

        if (this.state.retorno) {

            inserirClientes = (
                Object.keys(this.state.clientes)
                    .map((key, i) =>
                                <tbody key={key + i}  >
                                    <Clientes 
                                        remover={this.removerClienteHandler} 
                                        clienteInfo={this.state.clientes[key]} 
                                        clickRemove={() => this.fetchHandler()}
                                        indice={i}
                                    />
                                </tbody>
                        )
            )
        }

EDIT:

I started generating <tr> in the loop instead of <tbody> and the error persists, but now I am getting this:

Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tbody>

let inserirClientes = this.state.mensagem

    if (this.state.retorno) {

        inserirClientes = (
            Object.keys(this.state.clientes)
                .map((key, i) => (
                            <tr key={`${key}${i}`}  >
                                <Clientes 
                                    remover={this.removerClienteHandler} 
                                    clienteInfo={this.state.clientes[key]} 
                                    clickRemove={() => this.fetchHandler()}
                                    indice={i}
                                />
                            </tr>
                    ))
        )
        console.log(inserirClientes)
    }

    return (
        <div className="container">
            <div>
                <h2 style={{color: 'red'}}>Lista de Clientes</h2>
                <h4 style={{color: 'red'}}>OBS.: Verificar se é a melhor maneira de se criar tal tabela. Talvez seja possível criar um componente para isso</h4>
            </div>
            <br/>

            <table className="table table-bordered">
                <thead className="thead-dark">
                    <tr>
                        <th>#</th>
                        <th>Nome</th>
                        <th>Telefone</th>
                        <th>E-mail</th>
                        <th>Detalhar</th>
                        <th>Excluir</th>
                    </tr>
                </thead>
                <tbody>{inserirClientes}</tbody>
            </table>
        </div>
    );

Any idea how to solve this?

2 Answers

The answer lies with the initial value of inserirClientes.

Since you've initialized inserirClientes to be equal to this.state.mensagem, this component's first render will use that as the initial value, because Object.keys(this.state.clientes).map() has not yet had a chance to run.

I'm guessing these are your initial state values?

state = {
  mensagem: 'initial message',
  retorno: false,

  // other values...
}

If so, inserirClientes has the value of 'initial message' on first render, and you're effectively doing this:

<tbody>{'initial message'}</tbody>


Solution

Since you'd probably like to show the initial message before the data has loaded, we can simply provide the necessary markup around the initial message to make it valid inside <table>.

// since this will be a child of <tbody>, start with valid <tr> markup
let inserirClientes = <tr><td>{this.state.mensagem}</td></tr>

// if data has returned, replace initial message with array of <tr>s
if (this.state.retorno) {
  inserirClientes =
    Object.keys(this.state.clientes).map((key, i) => (
      <tr key={`${key}${i}`}  >
        <Clientes 
          remover={this.removerClienteHandler} 
          clienteInfo={this.state.clientes[key]} 
          clickRemove={() => this.fetchHandler()}
          indice={i}
        />
      </tr>
    ))
}

console.log(inserirClientes)

return (
  <div className="container">
    <div>
      <h2 style={{color: 'red'}}>Lista de Clientes</h2>
      <h4 style={{color: 'red'}}>OBS.: Verificar se é a melhor maneira de se criar tal tabela. Talvez seja possível criar um componente para isso</h4>
    </div>
    <br/>

    <table className="table table-bordered">
      <thead className="thead-dark">
        <tr>
          <th>#</th>
          <th>Nome</th>
          <th>Telefone</th>
          <th>E-mail</th>
          <th>Detalhar</th>
          <th>Excluir</th>
        </tr>
      </thead>
      <tbody>{inserirClientes}</tbody>
    </table>
  </div>
)

The correct structure for a table in HTML is

<table>
 <thead>
   <tr>
     <th>heading 1</th>
     <th>heading 2</th>
   </tr>
 </thead>
 <tbody>
   <tr>
     <td>col 1</td>
     <td>col 2</td>
   </tr>
 </tbody>
</table>

So you will need to add a <td> inside the <tr> to make it a valid DOM structure

inserirClientes =
      <tr>
        {Object.keys(this.state.clientes).map((key, i) => (
          <td key={`${key}${i}`}>
            <Clientes
              remover={this.removerClienteHandler}
              clienteInfo={this.state.clientes[key]}
              clickRemove={() => this.fetchHandler()}
              indice={i}
            />
          </td>
        ))}
      </tr>
Related