react / react-draft-wysiwyg: How to determine truly is blurred? - reactjs

The attached graphic shows my issue. If I click outside of the content, but inside the textarea, which you can see a light grey border around, the onBlur event is fired.
I've tried to capture the event and prevent this behaviour using target, but the event looks the same if you click way outside the box - where I want onBlur to fire.
So far using a ref has not worked either. I was hoping that would be the solution. Any ideas on how to allow a user to click anywhere within what they are seeing as the component react-draft-wysiwyg and NOT fire onBlur?

My fix, though feeling a bit hacky because of needing to handle the first onClickAway, was to elevate the onBlur call to a ClickAwayListener wrapping the Editor component like so:
<ClickAwayListener onClickAway={() => {
// Moving onBlur up to support clicking anywhere in component w/o blurring.
// Handle onClickAway firing once on focus of editor textarea.
if (firstClick) {
setFirstClick(false);
} else {
onBlur();
}
}}
>
<Editor
// do not use here: onBlur={onBlur}
// other props
/>
</ClickAwayListener>

Related

React global click track handler

I am new to react and working on a legacy codebase. Am wondering if we can write a global button click handler for click tracking.
The jQuery equivalent of this would be something like,
utilities.js :
var subscribeForClickTracking = function() {
$( "button" ).click((event) => { console.log($(event.target).html()) })
$( "p" ).click((event) => { console.log($(event.target).html()) })
}
In all the html files, will add reference to utiliies.js and this snippet of code.
$(document).ready(function () {
subscribeForClickTracking();
});
I have surfed about this and reached similar so questions, like
Higher Order React Component for Click Tracking
and https://www.freecodecamp.org/news/how-to-track-user-interactions-in-your-react-app-b82f0bc4c7ff/
But these solutions involve modifying the button's implementation, which would lead to huge change. (For a html form with 50+ buttons).
Is there an alternate approach to achieve something similar to the above jQuery approach.
Thanks in advance.
No, you can not do that. The reason is that React prevents you from doing global things to avoid side effects.
I think the proper React way would be to create your own Button component.
First create a new component :
export default Button = (props) => <button ...props />
Then, you can import and use Button instead of button in any component.
Then in your Button component, you can override your onClick method like this :
<button
...props
onClick={() => {
// doWhatYouWantHere;
props.onClick()
/>
However, as React is JavaScript, you can still use vanilla JavaScript to attach an event to every button

How to run common code before all onclick handlers in a React app? (to work around Safari middle-button click bug)

How can I run code before all onclick handlers in a React app without having to add code to each handler? Specifically, I want to globally make sure that middle-button clicks are ignored by all React onclick handlers. The goal is to work around a 12-year-old WebKit bug where Safari emits a click event when the middle mouse button is pressed, instead of the auxclick event that's mandated by the W3C standard and that's emitted by Chrome and Firefox.
Because some users accidentally trigger middle button clicks while scrolling with the mousewheel, I'd like to ignore these accidental clicks globally. How?
The code I want to inject is very simple:
if (e.button !== 0) {
e.stopPropagation();
}
But I'm not sure where to inject it so that it will run before all event handlers in my app.
One potential complication is that I don't want to ignore middle clicks completely (because browsers have a default behavior that middle clicking on an <a> element will open the link in a new tab). Instead, I just want to prevent react from doing anything with those invalid click events.
To solve this I thought I'd have to do something tricky like monkey-patching React, but it turned out that a non-tricky solution was possible: just wrap the entire app in a top-level component that captures click events using the onClickCapture event instead of the normal click event. Here's a simple component I wrote for this purpose.
IgnoreSafariMiddleClicks.tsx
import React, { useCallback, MouseEventHandler, ReactNode } from 'react';
export default function IgnoreSafariMiddleClicks({ children }: { children: ReactNode }) {
const onClick = useCallback<MouseEventHandler>(e => {
if (e.button !== 0) {
// Prevent middle clicks from being handled by click handlers on Safari
// browsers, in order to work around this 12-year-old WebKit bug:
// https://bugs.webkit.org/show_bug.cgi?id=22382
e.stopPropagation();
}
}, []);
return <div onClickCapture={onClick}>{children}</div>;
}
If you're not using TypeScript, here's a plain JS version of the component:
IgnoreSafariMiddleClicks.js
import React from 'react';
export default function IgnoreSafariMiddleClicks({ children }) {
const onClick = useCallback(e => {
if (e.button !== 0) {
// Prevent middle clicks from being handled by click handlers on Safari
// browsers, in order to work around this 12-year-old WebKit bug:
// https://bugs.webkit.org/show_bug.cgi?id=22382
e.stopPropagation();
}
}, []);
return <div onClickCapture={onClick}>{children}</div>;
}
Usage
import React from 'react';
import IgnoreSafariMiddleClicks from './IgnoreSafariMiddleClicks';
export default function App() {
return (
<IgnoreSafariMiddleClicks>
<div>
<button onClick={() => console.log('Left clicked!')}>
click me!
</button>
</div>
</IgnoreSafariMiddleClicks>
);
}
One gotcha I discovered was that SyntheticEvent.nativeEvent.stopImmediatePropagation doesn't work in this scenario, because other React event handlers continue to be called afterwards. I had to use the stopPropagation method of SyntheticEvent.
It took a while for me to figure out this solution (especially the capture-phase trick and the stopPropagation vs. stopImmediatePropagation issue), and I didn't see this middle-button-swallowing solution anywhere else online, so posting it here to help the next person searching for a solution.
An alternative solution could be to add a polyfill that replaced Safari's bad click events with standards-compliant auxclick events, but Google didn't return anything promising and writing an event polyfill is beyond my limited knowledge of React's event handling, so I opted for the wrapper-component solution above.

Why is "ripple effect" from Material-UI not visible when used in Popup (inside Leaflet map)?

I'm trying to put Material-UI Button inside Popup (Leaflet library).
When I'm doing it outside Popup => everything works fine, each button click triggers ripple effect.
When I'm trying to put the same code inside marker popup, something destroyes (overrides) ripple style and its's no longer visible.
Is it possible to somehow fix this problem?
Here's my codesandbox: https://codesandbox.io/s/react-leaflet-ripple-ivsxy
I have two buttons here:
(1) Outside the popup - works OK
(2) In the popup (popup is visible after click on marker) - button is visible but ripple effect is broken
Regarding why, in Material-UI ripple handler is triggered on mousedown event (from ButtonBase.js):
const handleMouseDown = useRippleHandler('start', onMouseDown);
but in leaflet mousedown event inside popup doesn't propagate outside the map in leaflet and this is by design (refer this thread for some details), that's the reason why ripple effect is missing.
To circumvent this behavior, one option would be to restore mousedown event propagation as demonstrated in the following Popup custom component:
class MyPopup extends Component {
componentDidMount() {
const { map } = this.props.leaflet;
map.on("popupopen", e => {
L.DomEvent.off(
this.getWrapper(),
"mousedown",
L.DomEvent.stopPropagation
);
});
map.on("popupclose", e => {
L.DomEvent.on(this.getWrapper(), "mousedown", L.DomEvent.stopPropagation);
});
}
getWrapper() {
return document.querySelector(".leaflet-popup-content-wrapper");
}
render() {
return <Popup {...this.props} />;
}
}
Here is an updated CodeSandbox

React dropzone - dragLeave event fired when dragging file over dropzone

I am using React dropzone for file upload
<DropZone
accept='.pdf,.pptx,.ppt,.docx,.doc,.xls,.xlsx,.xslx,.png,.xsl,.jpg,.jpeg,.gif,.zip'
onDrop={ files => {
this.handleFileDrop(files);
this.dragLeaveHandler();
} }
onDragEnter={ this.dragOverHandler }
onDragLeave={ this.dragLeaveHandler }
multiple={ false }
style={ { height: '100%' } }
>
dragOverHandler = () => {
console.log('enter');
this.setState({
isDragOver: true,
});
};
dragLeaveHandler = () => {
console.log('exit');
this.setState({
isDragOver: false,
});
};
When a file is moving above the drop zone onDragLeave event fires simultaneously.
Should I use some other events?
How can I fix this issue?
You could use pointer-events: none; on the element(s) that are firing the drag leave. That should still allow the dropped event and getting the accepted file though would stop overriding the dropzone events.
The problem you're facing is most likely caused by the DOM events dragEnter and dragLeave getting messed up instead of any flaw in the react-dropzone package. Some elements may cause hovering over them in certain positions not to register as hovering over their parent element. For example, there is a thin sliver at the top edge of any plain string rendered inside a block displayed element. Most commonly this happens inside a <p> tag.
Without seeing the children rendered inside your dropzone, it is impossible to give a specific fix. Generally, you will have to mess with the styling of the children, though. <p> tags for example will not be a problem if their size is set to 0 pixels or if they're replaced with <span> tags. Both options will disrupt the displaying of the children, which is unfortunatley unavoidable.
As for using other events, you're out of luck. The DropZone component relies on the onDragEnter and onDragLeave HTML DOM events. Therefore any fix you might come up with won't fix the component itself.
All in all, it's an unfortunate issue that just has to be dealt with. The simplest way to deal with it is to just have at most one piece of text inside the dropzone and to set its size to 0 pixels with css: height: 0px;. Regular <div> elements won't cause issues, so you can craft an intricate dropzone using them.

How to handle focus using declarative/functional style libraries like Redux and ReactJS?

In looking around to see what ways other developers are handling input focus when working with Redux I've come across some general guidance for ReactJS components such as this. My concern however is that the focus() function is imperative and I could see strange behaviours possible where multiple components are fighting over focus. Is there a redux way of dealing with focus? Is anybody dealing with pragmatically setting focus using redux and react and if so what techniques do you use?
Related:
How to set focus on an element in Elm?
Automatically focus input element after creation in purescript-halogen
https://github.com/cyclejs/cycle-core/issues/153
My approach is using ref callback, which is kind of an onRenderComplete of an element. In that callback I can focus (conditionally, if needed) and gain a reference for future focusing.
If the input is rendered conditionally after an action runs, that ref callback should fire a focus, because the ref doesn't exist yet immediately after calling the action, but only after render is done. Dealing with componentDidUpdate for things like focus just seems like a mess.
// Composer.jsx -- contains an input that will need to be focused somewhere else
class Composer extends Component {
render() {
return <input type="text" ref="input" />
}
// exposed as a public method
focus() {
this.refs.input.focus()
}
}
// App.jsx
#connect(
state => ({ isComposing: state.isComposing }),
...
)
class App extends Component {
render() {
const { isComposing } = this.props // or props, doesn't matter
return (
<div>
<button onClick={::this._onCompose}>Compose</button>
{isComposing ? <Composer ref={c => {
this._composer = c
this._composer && this._composer.focus() // issue initial focus
}} /> : null}
</div>
)
}
_onCompose() {
this.props.startComposing() // fire an action that changes state.isComposing
// the first time the action dispatches, this._composer is still null, so the ref takes care of the focus. After the render, the ref remains so it can be accessed:
this._composer && this._composer.focus() // focus if ref already exists
}
}
Why not autoFocus or isFocued prop?
As HTMLInputElement has value as a prop, but focus() as a method -- and not isFocused prop -- I would keep using methods to handle that. isFocused can get a value but if the user blurs from the input, what happens to that value? It'll be out of sync. Also, as mentioned in the comments, autoFocus can conflict with multiple components
So how to decide between props and methods?
For most cases props will be the answer. Methods can be used only in a 'fire and forget' things, such as scrollToBottom in a chat when a new message comes in, scrollIntoView and such. These are one time behaviors that the store doesn't care about and the user can change with an interaction, so a boolean prop won't fit. For all other things, I'd go with props.
Here's a jsbin:
http://jsbin.com/waholo/edit?html,js,output

Resources