Peter Perot Peter Perot - 5 months ago 52
HTML Question

React and blur event

I have a simple issue with React and event handling. My component looks like this (basically a table):

const MyList = ({ items, onBlur }) =>
<table onBlur={onBlur}}>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Publisher</th>
<th>Year</th>
<th>Author</th>
<th>System</th>
<th/>
</tr>
</thead>
<tbody>
{items.map(item => <MyListRow key={item.Id} item={item}/>)}
</tbody>
</table>;


I want the
blur
event to fire only if the focus goes out of the table. Instead the event fires on each child element of the table when it loses focus.

According to the docs React lets focus events bubble up.

The question is: How can I get my
onBlur
method fire only when the focus gets out of the table? IOW: How can I filter out and discard the unwanted events bubbling up so that I reveal only the events which indicate a lost of focus for the table?

Answer

The problem is that a table doesn't actually have a concept of focus since it's not an input itself.

When the onBlur fires on the contained inputs we will check the relatedTarget of the onBlur event which should be set to the element that has RECEIVED focus (or null). We then use a function that will traverse upwards through parentNodes from that newly focused element and ensure that our event's currentTarget (the table) is not an ancestor of the newly focused element. If the condition passes it is assumed that the table no longer has any focus.

const focusInCurrentTarget = ({ relatedTarget, currentTarget }) => {
  if (relatedTarget === null) return false;
  
  var node = relatedTarget.parentNode;
        
  while (node !== null) {
    if (node === currentTarget) return true;
    node = node.parentNode;
  }

  return false;
}

const onBlur = (e) => {
  if (!focusInCurrentTarget(e)) {
    console.log('table blurred');
  }
}

const MyList = ({ items, onBlur }) => (
  <table onBlur={onBlur}>
    <thead>
      <tr>
        <th>ID</th>
        <th>Title</th>
        <th>Publisher</th>
        <th>Year</th>
        <th>Author</th>
        <th>System</th>
        <th/>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
      </tr>
    </tbody>
  </table>
);
    
ReactDOM.render(
  <MyList onBlur={onBlur} />,
  document.getElementById('root')
);
table {
  padding: 10px;
  border: 1px solid red;
}
<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>
<br />
<input type="text" />

References:

UPDATED:

Removed use of ReactDOM.findDOMNode