How to add HTML with onClick event in React

Asked

Viewed 2,526 times

1

It may seem like a repeated question, but the examples I found are complicated and need a predefined array, I don’t have that, I wonder what is the simplest way to add an element to the DOM with React, knowing that it is not like jQuery and I must change the state of the component.

Update 1: I’m adding code to explain to Marcelo better my problem.

<tr>
  <td>
    <InputMask maxlenght="10" className="form-control" mask="99/99/9999"  type="text"/>
  </td>
  <td>
    <InputMask style={{'fontSize': '13px'}} mask="9999/99-99999"   className="form-control" type="text"/>
  </td>
  <td>
    <InputMask maxlenght="10" className="form-control"  type="text"/>
  </td>
  <td >
    <InputMask className="form-control"  type="text"/>
  </td>
  <td>
    <InputMask className="form-control somatorio"  maskChar="0" mask="99:99" type="text"/>
  </td>
</tr>

Well, I’d like to add this tr to a tbody, whenever the user clicks on a button, but when adding, it deletes the already existing content in tbody.

Update 2: I changed the way it’s done, using state, follow the code

Component

constructor(props) {
  super(props);
  this.state = {
    rows: []
  };
}

insereRow() {
  this.setState(prevState => ({
    rows: [...prevState.rows, 'row1']
  }))
}

Rendering in HTML, through the map

{this.state.rows.map((rows) =>
<tr key={rows}>
  <td>
    {rows}
    <InputMask maxlenght="10" className="form-control" mask="99/99/9999"  type="text"/>
  </td>
  <td>
    <InputMask style={{'fontSize': '13px'}} mask="9999/99-99999"   className="form-control" type="text"/>
  </td>
  <td>
    <InputMask maxlenght="10" className="form-control"  type="text"/>
  </td>
  <td >
    <InputMask className="form-control"  type="text"/>
  </td>
  <td>
    <InputMask className="form-control somatorio"  maskchar="0" mask="99:99" type="text"/>
  </td>
</tr>
)}

Button that should add an item to the array, so that it is rendered another tr

<button type="button" onClick={this.insereRow} className="btn btn-primary"><i className="glyphicon glyphicon-plus"></i> Atividades
                </button>

However do this error on this.setState line = this is Undefined

  • Nothing better than the documentation, but your question is not clear, what problem are you facing? Have you tried something ? Enter the code so we can help you. See on documentation how to render the HTML.

  • But you just want to add this tr ? Are you sure you don’t want to add another one. If you just want to add one, then I already answered your question

  • I would like to add other yes @Marcelorafael

  • These TRS, they will all be the same or will change as you click ?

  • All the same, @Marcelorafael. I will update the question with my new results. I did it in a different way

3 answers

1

You must use state, it shall contain data specific to the component which may change during the use of the component.

State must be a simple Javascript object.

For better understanding, we will create a component to work with contacts.

  1. Create a property on the object state

    this.state = {
      contatos: []
    };
    
  2. Create a method to add a new object in the state contatos and other methods to update these objects.

    // Método para adicionar novo objeto no estado "contatos"
    newContato = () => {
       let contatos = this.state.contatos;
       contatos.push({ nome: '', fone: '' });
       this.setState({ contatos: contatos });
    }
    
    // Método para atualizar o valor da propriedade "nome"
    // no objeto "N" do estado "contatos"
    // "i": índice, "e": event
    editNome = (i, e) =>  {
       let contatos = this.state.contatos;
       contatos[i].nome = e.target.value;
       this.setState({ contatos: contatos });
    }
    
    // Método para atualizar o valor da propriedade "fone"
    // no objeto "N" do estado "contatos"
    // "i": índice, "e": event
    editFone = (i, e) =>  {
       let contatos = this.state.contatos;
       contatos[i].fone = e.target.value;
       this.setState({ contatos: contatos });
    }
    
  3. HTML template

    {this.state.contatos.map((c, i) =>
    <div key={i}>
      <p>
        <label>#{i} - Nome</label>
        <input type="text" value={c.nome} onChange={e => this.editNome(i, e)} />
      </p>
      <p>
        <label>#{i} - Fone</label>
        <input type="text" value={c.fone} onChange={e => this.editFone(i, e)} />
      </p>
    </div>
    )}
    <button onClick={this.newContato}>Add Contato</button>
    

Note that when adding a new object in the state contatos the React automatically renders this new object on the screen without calling the method ReactDOM.render again.

Working example:

class Contatos extends React.Component {
  constructor() {
    super();
    this.state = {
      contatos: []
    };
  }

  editNome = (i, e) => {
    let contatos = this.state.contatos;
    contatos[i].nome = e.target.value;
    this.setState({ contatos: contatos });
  }

  editFone = (i, e) => {
    let contatos = this.state.contatos;
    contatos[i].fone = e.target.value;
    this.setState({ contatos: contatos });
  }

  newContato = () => {
    let contatos = this.state.contatos;
    contatos.push({ nome: '', fone: '' });
    this.setState({ contatos: contatos });
  }

  render() {
    return (
      <div>
        <div className="esquerda">
        {this.state.contatos.map((c, i) =>
          <div key={i}>
            <p>
              <label>#{i} - Nome</label>
              <input type="text" value={c.nome} onChange={e => this.editNome(i, e)} />
            </p>
            <p>
              <label>#{i} - Fone</label>
              <input type="text" value={c.fone} onChange={e => this.editFone(i, e)} />
            </p>
          </div>
        )}
          <button onClick={this.newContato}>Add Contato</button>
        </div>
        <pre className="direita">
          <code>{JSON.stringify(this.state, null, 2)}</code>
        </pre>
      </div>
    );
  }
}

ReactDOM.render(<Contatos />, document.getElementById('root'));
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 0;
}
.esquerda,
.direita {
  float: left;
  width: 50vw;
}
.esquerda > div {
  padding: 1em;
  padding-bottom: 0;
}
p, input {
  font-family: Lato;
}
p {
  margin: 0;
  margin-bottom: 10px;
}
label {
  display: block;
}

pre {
  background: #f3f3f3;
  padding: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

References

1

Here in my case I created a Component App and a call Hello, what I’ve done here is:

-When the user clicks <h1>Welcome...</h1>, will be rendered with ReactDOM.render the component Hello

class Hello extends React.Component {
  render()
  {
    return(
      <h1>Hello, I was inserted beibi :D</h1>
    );
  }
}
class App extends React.Component {
  insertHtml() {
    let target = document.querySelector(".target");

    ReactDOM.render(<Hello/>, target);
  }
  render() {
    return(
      <div>
        <h1 onClick={this.insertHtml.bind(this)}>
          Welcome, click over me
        </h1>

        <div className={"target"}></div>
      </div>
    );
  }
}

ReactDOM.render(<App/>, document.querySelector(".root"));
<div class="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

You could use:

target.innerHTML = renderToString(<Hello/>);

But how will you want to use the "<Hello/>" to be dynamic, so here it is not recommended to use renderToString. renderToString is more to Sever Side Rendering

  • Thanks for the answer Marcelo, already solved 75% of the problem. However, I have another problem, which is the following: I want to render a tr, in a tbody through a click, as you(very well) showed, but the same erases other trs that have already been rendered differently. How to render an html tag without deleting its content? Thanks in advance

  • You can create an array of trs and put inside the component that works smoothly. Example: Each click, you can create a new tr with document.createElement('tr'), puts the value in it and adds inside an array, at the end only add this array inside the @Thiagosouza component

  • Marcelo, this tr, is an Row with various inputs and things like that. How would I do that if I don’t want to create a simple tr?

  • There are several ways, you can create another component, or you can do what I told you is by creating an element with Document.createelement() and adding children that are also Document.createelement(), using createelement will get a little difficult, I’d rather create a new component. But I don’t know exactly what you want to do. @Thiagosouza

  • I changed the answer so you could see better

0

The answer is simple, just read the documentation:

constructor(){
     //Você tem de dizer quem é "this"
     this.insereRow = this.insereRow.bind(this);
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.