Using ResizeObserver in React class component - javascript

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>;

Related

Passing a parameter to an event handler in the parent class from a child class ReactJS

I am trying to pass a parameter to an event handler in a parent class, but am having some difficulty. I have done a good amount of research and am close to answer, but something's not quite working. Below I will give a basic hypothetical example of what I would like to do that doesn't work.
class Parent extends React.Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(i){
return event => console.log(i);
}
render(){
return <Child onClick={this.handleClick}></button>;
}
}
class Child extends React.Component {
render() {
const myVar = 2;
return <button onClick={this.props.onClick(myVar)}></button>;
}
}
I know the onClick prop that is passed to the Child is not a function, so I am unable to pass parameters directly to it. What is the best way to go about doing this? Thanks for you help!
Make the following update to your code
class Child extends React.Component{
render(){
const var = 2;
return <button onClick={ () => {this.props.onClick(var)} }</button>;
}
}
Furthermore, you should refactor the following method used by parent as so. You are passing e needlessly.
handleClick(i){
console.log(i);
}
The onClick on your child component was immediately invoked instead of waiting for the onClick event to fire.
You can't do
handleClick(i){
return (e => {console.log(i)});
}
instead, try
handleClick(i){
console.log(i)
}
and move the event handling to where it is being called. So instead of
<button onClick={this.props.onClick(var)}</button>
You want to try
<button onClick={e => this.props.onClick(var)}</button>

How to make setInterval function triggered once while componentDidUpdate is fired 2 times

I'm working on a React app, and I want to manage the user inactivity.
For it, I defined a countdown which is supposed to be reset to his original value if the user is doing something in the App.
The displayed components are rendered/surrounded by a Layout component.
My problem is that the layout is updated twice after every user action, since the .setState function is used in it. Therefore, the InactivityManager Component also is updated twice and the setInterval is executed twice at the same time.
I wrote a simple InactivityManager Component, which isn't rendering anything but is rendered in the Layout.
Here is the component:
import { Component } from 'react';
import { isLogged, logout } from '...';
class InactivityManager extends Component {
constructor(props) {
super(props);
this.refreshRemainingTime = this.refreshRemainingTime.bind(this);
}
componentDidUpdate() {
if (isLogged()) {
clearInterval(this.refreshRemainingTime);
localStorage.setItem('activityCountdown', '900');
window.setInterval(this.refreshRemainingTime, 5000);
}
}
refreshRemainingTime = () => {
let activityCountdown = parseInt(localStorage.getItem('activityCountdown'), 10);
activityCountdown -= 60;
localStorage.setItem('activityCountdown', activityCountdown);
if (activityCountdown <= 0) {
logout();
localStorage.removeItem('activityCountdown');
}
};
render() {
return null;
}
}
export default InactivityManager;
Any idea of what is the best approach? Also tried to include the Component in the App.js but the Component expect only one "Route" child.
Thanks!
Can you modify the componentDidUpdate method a little bit.
componentDidUpdate() {
if (isLogged()) {
if (this.interval) clearInterval(this.interval);
localStorage.setItem('activityCountdown', '900');
this.interval = setInterval(this.refreshRemainingTime, 5000);
}
}
I finally prefered to use setTimeout instead of setInterval, there is no problem anymore with this.
Thanks!

Call a method within a React Component from Electron's globalShortcut

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>

How to organize actions that don't modify state?

let's say I have a react component like this:
class App extends Component {
print = () => {
const { url } = this.props
const frame = document.createElement('iframe')
frame.addEventListener('load', () => {
const win = frame.contentWindow
win.focus()
win.print()
win.addEventListener('focus', () => document.body.removeChild(frame))
})
Object.assign(frame.style, {
visibility: 'hidden',
position: 'fixed',
right: 0,
bottom: 0
})
frame.src = url
document.body.appendChild(frame)
}
}
Basically, clicking a button calls the print function in the browser for the user. In a situation like this, do I still make this into a redux action like DO_PRINT that doesn't actually do anything to my redux state or do I just not bother with it?
For your particular example, I would avoid creating a Redux action as there is no need for that DO_PRINT to update any state if it is only calling window.print().
In fact, assuming you're creating a "Print button" component, I would redefine this as a dumb component. (See differences between presentationl and container components.)
import React from ‘react’;
const PrintButton = () => {
const onClick = () => {
window.print();
};
return <button onClick={onClick}>Click Me</button>
};
export default PrintButton;
FYI, the code above might not be the most efficient way of declaring event handlers for stateless components as the function is potentilly called each time the component is rendered. There might be better (more efficient) ways (described in another SO question) but that's beyond this question.

ReactJS bind a component method the correct way

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

Categories