JoeTidee
JoeTidee

Reputation: 26124

React - how to trigger preventDefault() before the synthetic event is recycled?

I have the following function within a React component which is run when a link is clicked:

onClick(e, { name }) {
    e.stopPropagation();

    const doIt = await checkSomething();

    if (doIt) {
        e.preventDefault();
        doSomethingElse();
    }
}

Problem is that by the time e.preventDefault() is reached the synthetic event is recycled. So, how do I tell the browser not to follow the link if it is clicked, when some logic is needed to calculate this decision and before the synthetic event is recycled?

Upvotes: 6

Views: 4738

Answers (4)

StefaDesign
StefaDesign

Reputation: 959

Have you tried using promises and .then():

// Async/Await
const asyncGreeting = async () => 'Greetings';

// Promises
const promiseGreeting = () => new Promise(((resolve) => {
  resolve('Greetings');
}));

asyncGreeting().then(result => console.log(result));
promiseGreeting().then(result => console.log(result));

You should be able to control preventDefault();

Upvotes: 0

Sagiv b.g
Sagiv b.g

Reputation: 31024

I'm just throwing an idea here.

If you can't or don't want to do the URL redirect programmatically, then there is an alternative.
You can nest a function (currying) and invoke the second function only after you finish your logic.
Note that this will require you to write your nested function as an IIFE.


Here is a small proof of concept using JavaScript (open console):

const el = document.getElementById("link");


function onLinkClick() {
  console.log('doing some stuff..');
  for(let i = 0; i < 500; i++){
  	console.log(`doing more stuff #${i}`);  
  }
  return function(e) {
    // this will trigger with the actuall event (note this is an IIFE)
    console.clear();
  }();
}

el.addEventListener('click', onLinkClick);
#big {
  min-height: 950px;
}
<div id="big">
  <a id="link" href="#test">Click to get to the bottom of the page</a>
</div>

<div id="test">Bottom of page</div>


Example with react:

class App extends React.Component {

  onClick = (e) => {
    console.log('doing some stuff..');
    for (let i = 0; i < 500; i++) {
      console.log(`doing more stuff #${i}`);
    }
    return ((e) => {
      //console.log(e, "inner function")
      console.clear();
    })(e)
  }

  render() {
    return (
      <div>
        <div style={{ height: '950px' }}>
          <a href="#test" onClick={this.onClick} >Click to get to the bottom of the page</a>
        </div>

        <div id="test">Bottom of page</div>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>

Upvotes: 2

Shubham Khatri
Shubham Khatri

Reputation: 282000

One way to handle this would be to use preventDefault and when the action is complete and you need to route, call the Routing logic yourself using history.push()

onClick(e, { name }) {
    e.stopPropagation();
    e.preventDefault();
    const doIt = await checkSomething();

    if (doIt) {
        doSomethingElse();
    } else {
        this.props.history.push('/pathToRoute');
    }
}

Check this answer on how to Programmatically navigate with React-router

Upvotes: 2

Kyle Richardson
Kyle Richardson

Reputation: 5645

To use preventDefault on an event, you need to do it before the callback function returns. This is not something you can do after the completion of an asynchronous task.

If you look at MDN Event.preventDefault Notes, you'll notice it says you have to call it somewhere in the event flow.

Calling preventDefault() during any stage of event flow cancels the event, meaning that any default action normally taken by the implementation as a result of the event will not occur.

You can also refer to this SO question: event.preventDefault in async functions . In the top answer, you'll see mention of the use of await and how the event.preventDefault() needs to occur before the first await statement. This holds true for using promises, or any asynchronous action.

As an aside:

If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.

Here is the SyntheticEvent documentation.

Upvotes: 1

Related Questions