Jest test causing a 'synthetic event' error when checking a spied on function was called with an object containing 'currentTarget'

Viewed 149

I have a function like this:

const doThingWithTarget = (event, someBoolean) => {
  if(someBoolean) otherFunction(event.currentTarget); // uses currentTarget
}

// more code ...

return (<Button onClick={event => doThingEithEvent(event, true)}>Click me!</Button>)

And I am having to test this button was called clicked, and I wanted to make sure the first argument has a currentTarget:

expect(doThingWithTarget).toHaveBeenCalledWith(
  expect.objectContaining({ currentTarget: expect.any(Function) }), true,
);

But react is detecting me accessing the currentTarget on the synthetic element and throwing an error.

I can't remember how I created this error, but some searching brings up this familiar message:

Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property target on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist().

Except mine said 'currentTarget'.

How can I avoid that? It shouldn't be erroring.

1 Answers

This is actually a valid warning from react, as explained in the docs or in this article; the event losses all data after the handler has run. Since doThingWithTarget could be an async function your code would break on that scenario and somehow jest manages to replicate it.

Your options would be getting the data from the event inside the event handler and then passing it to the function:

  const handler = (event) => {
    const currentTarget = event.currentTarget;
    doThingWithTarget({currentTarget}, true);
  };

  return <button onClick={handler}>Click me!</button>;

Or call event.persist() in the event handler to keep the data, but this will force you to rewrite your test a little.

  const handler = (event) => {
    event.persist();
    doThingWithTarget(event, true);
  };

  return <button onClick={handler}>Click me!</button>;
expect(doThingWithTarget.mock.calls[0][0]).toHaveProperty("currentTarget");
expect(doThingWithTarget.mock.calls[0][1]).toBe(true);

I had to change it to use mock.calls to check the first arg has the currentTarget property defined and the second is true because, for some reason, the currentTarget comes as null even when calling event.persist()

You could also do

  expect(doThingWithTarget).toHaveBeenCalledWith(
    expect.objectContaining({ currentTarget: null }),
    true
  );

But not sure if the currentTarget will always be null.

https://codesandbox.io/s/synthetic-event-warning-mrs9t4?file=/src/__tests__/hello.js

Related