kranj doo
kranj doo

Reputation: 49

Preventing refresh page on submit in React

I'm trying to create an editable table that will turn a specific cell in to an <input> once it's clicked and then will run the handleSubmit() method once the user presses return.

Below is an example of a <td> cell using an onClick event to run handleClick() method and turn it's <td></td> in to <form><input></input></form>.

<td onClick={ e => this.handleClick(e)} style={{padding:'5px'}} key={cellID} id={cellID}>{frame.rows[i][idx]}</td>
      
 handleClick(e:React.MouseEvent<HTMLTableDataCellElement, MouseEvent>) {
    if(this.state.editing == false){          
      let form = `<form onSubmit=${ (e:any) => {this.handleSubmit(e)} } ><input type="text" value=${e.currentTarget.innerText} className="input-small gf-form-input width-auto"/></form>`
      e.currentTarget.innerHTML =  form;         
    }       
    this.setState({editing: true})
  }

 handleSubmit(e){
    e.preventDefault()        
  }

Using e.preventDefault() does not seem to work in this instance. Every time i press return after changing the text, the page refreshes. How do i stop the page from refreshing in this instance?

Upvotes: 1

Views: 1722

Answers (3)

ikos23
ikos23

Reputation: 5354

There are a few issues in your code. I'd better fix them, rather than trying to fix the issue with the form submission. And once it is done, you won't have to fix the issue with the form - there simply won't be any.

First, let's take a look into your editable cell:

<td onClick={ e => this.handleClick(e)} style={{padding:'5px'}} key={cellID} id={cellID}>{frame.rows[i][idx]}</td>

This element should be rendered differently, based on some state. We can achieve this easily with React:

// JSX snippet
<td onClick={ e => this.handleClick(e)} 
    style={{padding:'5px'}} 
    key={cellID} id={cellID}>
    {this.state.editing ? <Input ... /> : <Label ... />}
</td>

I do not provide all the code, because I believe the components are self-explainable (and you are welcome to name them as you'd like to, I give them very simple names to make their aim clear).

  • <Input /> encapsulates everything related to editing logic
  • <Label /> simply renders a text or whatever you need (probably frame.rows[i][idx])
  • ... means that they will most probably have some values/handlers passed as props

In your code, you have this:

let form = `<form onSubmit=${ (e:any) => {this.handleSubmit(e)} } ><input type="text" value=${e.currentTarget.innerText} className="input-small gf-form-input width-auto"/></form>`

I believe it deserves to be a separate component with its own state and logic (e.g. submit). In fact, this is what <Input ... /> is in my example. And if you make it as a separate component - the following code will work (because it will be a part of that separate component):

handleSubmit(e) {
  e.preventDefault()        
}

Finally, avoid doing something like that:

e.currentTarget.innerHTML =  form;

Reconsider your approach and you simply won't need to do something like that.

Upvotes: 1

codingwithmanny
codingwithmanny

Reputation: 1184

I'm guessing you're wanting to achieve something where you can editing columns, modify or abandon changes, and then update things as needed.

This example is with local state, but you could still do it with fetching data.

Click the "Run code snippet" below to see a working example.

// main.js

const { useState } = React;

const App = () => {
  // State
  const [data, setData] = useState([{ id: 1, name: 'John', editing: false }, { id: 2, name: 'Kevin', editing: false }]);
   
  // Functions
  const onSubmitForm = index => event => {
    // To prevent form submission
    event.preventDefault();
    // To prevent td onClick
    event.stopPropagation();
    
    const newData = [...data];
    newData[index].name = newData[index].temp;
    newData[index].editing = false;
    delete newData[index].temp;
    setData(newData);
  }
  
  const onClickToggleEditing = index => event => {
    // To prevent td onClick and button conflicting with each other for toggling back on
    event.stopPropagation();
    
    const newData = [...data];
    newData[index].editing = !newData[index].editing;
    newData[index].temp = newData[index].name;
    setData(newData);
  }
  
  const onInputChange = index => event => {
    const newData = [...data];
    newData[index].temp = event.target.value;
    setData(newData);    
  }
   
  // Render
  // This basically like having its own component
  const editing = ( data, index, onChange, onSubmit, onCancel) => {  
    const onKeyUp = index => event => {
       if (event.key === 'Escape') {
        onCancel(index)(event);
       }
    }
  
    return <form onSubmit={onSubmit(index)}><input onKeyUp={onKeyUp(index)} onClick={e => e.stopPropagation()} type="text" value={data.temp} placeholder="Enter text" onChange={onChange(index)} /><button onClick={onSubmit(index)} type="submit">Save</button><button type="button" onClick={onCancel(index)}>Cancel</button></form>
  }
  
  return <main>
    <h1>Table Editing</h1>
    <p><small>Click to edit cell for <b>Name</b>.</small></p>
    <table>
      <thead>
        <tr>
        <th>ID</th>
        <th>Name</th>
        </tr>
      </thead>
        {data && data.length > 0 && <tbody>
           {data.map((i, k) => <tr key={`row-${k}`}>
            <td>{i.id}</td>
            <td className="editable" onClick={onClickToggleEditing(k)}>{i.editing ? editing(i, k, onInputChange, onSubmitForm, onClickToggleEditing) : i.name}</td>
          </tr>)}
      </tbody>}
    </table>
    
    <hr />
    <p><b>Data Manipulation:</b></p>
    <pre><code>{JSON.stringify(data, null, '\t')}</code></pre>
    
  </main>
}

ReactDOM.render(<App />, document.querySelector('#root'));
body {
  padding: 0;
  margin: 0;
  font-family: Arial,sans-serif;
}

main {
  padding: 0 20px;
}

h1 {
  font-size: 18px;
}


table {
  width: 100%;
  border-spacing: 0;
}

table tr td,
table tr th {
  border: 1px solid #efefef;
  height: 30px;
  line-height: 30px;
  text-align: left;
  padding: 6px;
}

table tr th:first-child {
  width: 100px;
}

.editable:hover {
  background: #efefef;
  cursor: pointer;
}

table input {
  height: 30px;
  line-height: 30px;
  font-size: 14px;
  padding: 0 6px;
  margin-right: 6px;
}

table button {
  height: 32px;
  border: none;
  background: red;
  color: white;
  font-size: 14px;
  padding: 0 10px;
  border-radius: 4px;
  margin-right: 5px;
  cursor: pointer;
}

table button[type=submit] {
  height: 32px;
  border: none;
  background: green;
  color: white;
  font-size: 14px;
  padding: 0 10px;
  border-radius: 4px;
}

hr {
  border: none;
  height: 1px;
  background: #999;
  margin: 20px 0;
}

pre {
  background: #efefef;
  padding: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>

Upvotes: 2

shahrooz bazrafshan
shahrooz bazrafshan

Reputation: 708

hi you can use it like below:

1- i assume you have a return button like below so you can submit in return not using form submit event:

_handleReturn(){
let val = document.getElementById("your input id");
//you can post above text to server if you want
   //Do Somthing
}
<button id="btn_return" onClick={this._handleReturn} />

2- i don't see where you trigger handleSubmit, but submitting form cause refresh, you should use ajax if you don't want.

Upvotes: 0

Related Questions