I have a React component with a method:
class Timer extends Component{
start(){
this.timerInterval=setInterval(this.tick,1000);
}
[...]
}
I want to be able to call the method start() whenever the user presses a combination of keys.
In my main.js file I have:
app.on('ready', function(){
createWindow();
globalShortcut('Cmd+Alt+K',function(){
//call start() here
})
});
How can I achieve this? There's not much information I could find on this subject.
When in need of use of electron library inside react you should import it using electron remote
const { globalShortcut } = require('electron').remote;
class Timer extends Component{
componentDidMount = () => {
globalShortcut.register('Cmd+Alt+K', () => {
//your call
this.start()
});
}
start(){
this.timerInterval=setInterval(this.tick,1000);
}
[...]
}
I was hoping someone with Electron-specific experience would address this, but it's been a full day now.
To cross the outside world / React component barrier, you're probably best off using a custom event. To do that, you'll need access to the DOM element created by React in response to your render call, then listen for your custom event directly on the element. Here's an example, see the comments for details. Note that in this example I'm passing an object as the event detail; that's just to show you can do that (not just simple strings), but you could just do {detail: "Tick #" + ticker} instead and use event.detail directly as the message.
class Example extends React.Component {
// Normal component setup
constructor(...args) {
super(...args);
this.state = {message: ""};
// Our event handler. (We could also define this at the class level using the class
// fields syntax that's currently at Stage 3 in the ECMA TC39 process; most React
// setups with JSX transpile class fields syntax.)
this.doSomething = event => {
this.setState({message: event.detail.message || ""});
};
}
// Render with a way of finding the element in the DOM from outside (in this case
// I'm using a class, but it could be an ID, or just knowing where it is in the DOM).
// We use a ref so we can hook the event when the elemet is created.
render() {
return <div className="foo" ref={element => this.element = element}>{this.state.message}</div>;
}
// Hook the event on mount, unhook it on unmount
componentDidMount() {
this.element.addEventListener("my-event", this.doSomething);
}
componentWillUnmount() {
this.element.removeEventListener("my-event", this.doSomething);
}
}
// Top-level app rendering
ReactDOM.render(<Example />, document.getElementById("root"));
// Demonstrate triggering the event, in our case we do it every half-second or so
let ticker = 0;
setInterval(() => {
const foo = document.querySelector("#root .foo");
++ticker;
foo.dispatchEvent(new CustomEvent("my-event", {detail: {message: "Tick #" + ticker}}));
}, 500);
<div id="root"></div>
<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>
Related
I am new to front-end testing, and I am trying to get a good grasp on it.
In the process, inside one project, I am creating a test for a Vue component. I want to test that the behaviour of the code is correct (The component has code inside the mounted() hook that has to perform basically some checks and an API call).
I want to check that the code reaches one method. Previously to that, the code creates a click event listener to one element in the DOM.
My test emulates a click event (triggers it), but it cannot assert that the proper method has been called after the click event.
This is due to it not finding the element in the DOM to which it has to add the event listener. It seems that the code cannot find anything inside the document (using .getElementById()).
I wonder why, and how I would resolve this, since I have been stuck here for hours and I haven't found any solution that could work here, even when I have learned some interesting things in the process. I will leave a code example with the code structure I have built:
Inside the component:
<template>
// ...
<button id = "myButton">Add</button>
</template>
<script>
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile ";
let classIESF = new classInExternalScriptsFile();
export default {
methods: {
setup: function () {
classIESF.setupMethod();
},
},
mounted() {
this.setupMethod();
},
};
</script>
Inside the scriptsFile
export class classInExternalScriptsFile {
setupMethod() {
let myButton = document.getElementById("myButton") // <-- getElementById() returns a null here
if (typeof myButton !== "undefined" && myButton !== null) {
myButton.onclick = () => { // <-- The test code complains because it cannot enter here
// Some lines...
this.mySuperMethod()
}
}
}
mySuperMethod() {
// API call etc.
}
}
Inside the .spec.js test file:
// imports...
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile.js";
describe("description...", () => {
const mySuperMethodMock = vi
.spyOn(classInExternalScriptsFile.prototype, "mySuperMethod")
.mockImplementation(() => {});
test("That the button performs x when clicked", () => {
let wrapper = mount(myComponent, {
props: ...,
});
let myButton = wrapper.find('[test-id="my-button"]');
myButton.trigger("click");
expect(mySuperMethodMock).toHaveBeenCalled(); // <-- The test fails here
}
}
can anyone provide a solution to the problem that I'm currently encountering? I created a custom element where this custom element must have been detected on the dom, but I need to have the data contained in this custom element loaded, so my program code is like this.
import './menu-item.js';
class MenuList extends HTMLElement {
// forEach cannot be used if I use the ConnectedCallback () method
connectedCallback() {
this.render()
}
// my data can be from this method setter
set menus(menus) {
this._menus = menus;
this.render();
}
render() {
this._menus.forEach(menu => {
const menuItemElement = document.createElement('menu-item');
menuItemElement.menu = menu;
this.appendChild(menuItemElement);
});
}
}
customElements.define('menu-list', MenuList);
and this is the data I sent in the main.js file
import '../component/menu/menu-list.js';
import polo from '../data/polo/polo.js';
const menuListElement = document.querySelector('menu-list');
menuListElement.menus = polo;
please give me the solution.
The connectedCallback runs before the menus=polo statement.
So there is no this._menus declared.
If all the menus setter does is call render, then why not merge them:
set menus(menus) {
this.append(...menus.map(menu => {
const menuItemElement = document.createElement('menu-item');
menuItemElement.menu = menu;
return menuItemElement;
}));
}
I am using React 15 on Chrome and want to hook up an event listener to detect changes to a parent container. After looking around for options, I came across ResizeObserver and am not sure how to get it to work in my project.
Currently, I am putting it in my constructor but it does not seem to print any text and I am not sure what to put in the observe call.
class MyComponent extends React.Component {
constructor(props) {
super(props);
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(somethingGoesHere);
}
render() {
return (
<AnotherComponent>
<YetAnotherComponent>
</YetAnotherComponent>
<CanYouBelieveIt>
</CanYouBelieveIt>
<RealComponent />
</AnotherComponent>
);
}
}
Ideally, I also don't want to wrap RealComponent in a div and give that div an id. Is there a way to the RealComponent directly?
My goal is to observe any resize changes to the RealComponent but MyComponent is fine too. What should I put in the somethingGoesHere slot?
EDIT:
For the sake of getting something to work, I bit the bullet and wrapped a div tag around RealComponent. I then gave it an id <div id="myDivTag"> and changed the observe call:
resizeObserver.observe(document.getElementById("myDivTag"));
However, when running this, I get:
Uncaught TypeError: resizeObserver.observe is not a function
Any help would be greatly appreciated.
ComponentDidMount would be the best place to set up your observer but you also want to disconnect on ComponentWillUnmount.
class MyComponent extends React.Component {
resizeObserver = null;
resizeElement = createRef();
componentDidMount() {
this.resizeObserver = new ResizeObserver((entries) => {
// do things
});
this.resizeObserver.observe(this.resizeElement.current);
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
render() {
return (
<div ref={this.resizeElement}>
...
</div>
);
}
}
EDIT: Davidicus's answer below is more complete, look there first
ResizeObserver can't go in the constructor because the div doesn't exist at that point in the component lifecycle.
I don't think you can get around the extra div because react components reduce to html elements anyway.
Put this in componentDidMount and it should work:
componentDidMount() {
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(document.getElementById("myDivTag"));
}
I was fighting a the similar problem recently with the difference that my app is predominantly using hooks and functional components.
Here is an example how to use the ResizeObserver within a React functional component (in typescript):
const resizeObserver = React.useRef<ResizeObserver>(new ResizeObserver((entries:ResizeObserverEntry[]) => {
// your code to handle the size change
}));
const resizedContainerRef = React.useCallback((container: HTMLDivElement) => {
if (container !== null) {
resizeObserver.current.observe(container);
}
// When element is unmounted, ref callback is called with a null argument
// => best time to cleanup the observer
else {
if (resizeObserver.current)
resizeObserver.current.disconnect();
}
}, [resizeObserver.current]);
return <div ref={resizedContainerRef}>
// Your component content here
</div>;
In my web application, I want to prompt user when he/she tries to close the browser/tab based upon Redux state using event handlers.
I am using the below code to prompt user before exiting based upon 'isLeaving' state.
function mapStateToProps(state) {
const {isLeaving} = state.app.getIn(['abc']);
return {
isLeaving
};
}
#connect(mapStateToProps, {}, undefined, {withRef: true})
export default class MyClass extends React.component {
#autobind
stayOnPage(event) {
if (this.props.isLeaving) {
const message = 'Are you sure you want to leave';
event.returnValue = message;
return message;
}
return false;
}
componentDidMount() {
window.addEventListener('beforeunload', (event) => {
this.stayOnPage(event);
});
}
componentWillUnmount() {
window.removeEventListener('beforeunload', (event) => {
this.stayOnPage(event);
});
}
componentWillReceiveProps(nextProps) {
if (this.props.prop1 !== nextProps.prop2) {
// do something
}
}
render() {
//
}
}
This code works fine. But whenever there is a change in prop1, I see that this.props.isLeaving does not have updated value.
Can somebody help? What is I'm doing wrong here?
You aren't cleaning up correctly in componentWillUnmount. The event handler you're trying to remove is a brand new function closure, not the same instance that you added. You should just attach the actual handler, rather than using an arrow function, like so:
componentDidMount() {
window.addEventListener('beforeunload', this.stayOnPage);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.stayOnPage);
}
Possibly what you are seeing is the event triggering on a stale component instance, so it has old state.
React uses synthetic events, basically events are recycled to be more performant. You can read more on that here
What I normally do is pass the value I need on invocation
Not sure if this will work in your specific case because I define my event handlers in JSX instead of accessing the window object (which you might want to do as well but to be honest I'm not sure) but I use this pattern all the time to handle e.target.value properly
I am trying to use .bind() when using a method in my component.
The reason is simple: In a loop I am returing Components and extend them with a property which is calling a method. But for every loop-item this I want to extend the this Object with some information (like a key).
Example:
Items.jsx
Items = React.createClass({
eventMethod() {
console.log('this event was triggered by key:', this.key);
},
items() {
let items = [];
let properties = {};
_.each(this.props.items, (itemData, key)=>{
properties.eventMethodInItem = this.eventMethod.bind(_.extend(this, {
key
}));
let {...props} = properties;
let item = <Item {...props} key={key} />;
items.push(item);
});
return items;
},
render() {
return(<div>{this.items()}</div>);
}
});
Item.jsx
Item = React.createClass(...);
In this case (and its working) when the Item Component is triggering the prop "eventMethodInItem" my method "eventMethod" will be called and this.key has the correct value!
So - whats now the question ? Its working perfect, right ?
Yes.
But ReactJS does not want me to do this. This is what ReactJS is telling me as a console log.
Warning: bind(): You are binding a component method to the component. React does this for you automatically in a high-performance way, so you can safely remove this call. See Items
Maybe you think its a "bad" way to add children to the component like I am doing it but in my special case I need to do this in this way - so I need to bind new information to a method.
I'm not going to pretend that I understand what you are trying to do here, but maybe I can help clear it up anyway.
React takes all of the top level methods found on each component and automagically binds them to the context of the component.
This prevents other methods from overriding the context of this and as a result, if you try to rebind the method, React says "Hey don't bother. I already got it" — which is the warning you are seeing.
Assuming that you really want do this (each time you are mutating the outer properties object by overriding the eventMethodInItem property).
properties.eventMethodInItem = this.eventMethod.bind(_.extend(this, {
key
}));
Then I can't see any reason that the eventMethod has to live on the component, rather than just in the scope of the items function.
items() {
const eventMethod = function() {
console.log('this event was triggered by key:', this.key);
}
// ...
_.each(this.props.items, (itemData, key)=>{
properties.eventMethodInItem = eventMethod.bind(_.extend(this, {
key
}));
// ...
});
},
That way you don't have to fight React to get your program to work.
React is already autobinding this when using React.createClass http://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#under-the-hood-autobinding-and-event-delegation
Change your binding to
properties.eventMethodInItem = this.eventMethod.bind(null,key);
and your eventMethod to
eventMethod(key) {
console.log('this event was triggered by key:', key);
}
I also suggest using _.map instead of _.each
items() {
return _.map(this.props.items, (itemData, key) => {
return <Item
handleEventMethod={this.eventMethod.bind(null,key)}
key={key} />;
});
},
Good pattern
https://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
Before :
class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
this. _handleFoo = this. _handleFoo.bind(this);
}
// ...
}
After :
class BaseComponent extends React.Component {
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
}
class ExampleComponent extends BaseComponent {
constructor() {
super();
this._bind('_handleClick', '_handleFoo');
}
// ...
}
another good hacks for this topic http://egorsmirnov.me/2015/08/16/react-and-es6-part3.html