JQuery function in ReactJS for a dynamic viewport height - javascript

so I got some help in another question on making a table's height equal to the viewport's height. Meaning, if the user resizes the screen, the table height adjusts on-the-fly to occupy the entire height of the screen. My problem now is, this is a React App and I am having a hard time converting this jquery function to React.
My function looks like this:
$(document).ready(function() {
function setHeight() {
windowHeight = $(window).innerHeight();
$('.dynamicHeight').css('height', windowHeight + 'px');
};
setHeight();
$(window).resize(function() {
setHeight();
});
});
Here is a codepen showing the behavior
And here is a screen shot of what I am trying to do
How can I build this function in React? I'm assuming it will need some modification.
Thanks in advance
NOTE: this may look like another question I made, but it is not a duplicate. It's an entirely different question related to the same issue.

In the components lifecycle, you should add a componentDidMount method. In that method, simply add a resize handler
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
width: $(window).width(),
height: $(window).height(),
}
this.resize = this.resize.bind(this);
}
resize(){
this.setState(() => {
return {
width: $(window).width(),
height: $(window).height()
}
});
}
componentDidMount(){
window.addEventListener('resize', this.resize);
}
render(){
return (
<div>
<MyComponent width={this.state.width} height={this.state.height} />
</div>
)
}
}
So what this does is, when your Component initializes, you set the state to the width and height of the viewport (using jQuery). Then you define a method to update the state, and when your component mounts you attach an resize event listener to it and call the resize method each time the screen resizes. This ensures that your state always contains the width and height of the viewport. Then you can define the width and height of your component by passing that state to your component as props and boom! All good, everytime you resize your screen your component will match it.
If you have any questions let me know. You might need to tinker with this to get it to work (specifically the lifecycle events might not update state as often as you need to) I just whipped it up and did not test it but in theory this is exactly what you need
edit: just a thought, if you want to change the style of your components I would use a style object. So in the child component, do something like this:
let style = {
width: this.props.width + 'px',
height: this.props.height + 'px'
}
And when you render that component
<ChildComponent style={style} />

Related

How can I make slotted child component grow/shrink with parent?

I have a navigation bar component with a variable width in the following format:
<template>
<div class="container">
<div v-if="!!$scopedSlots.menu">
<slot name="menu" />
</div>
...
</div>
</template>
I am creating a new component that can be slotted in to that "menu" slot which should function as a button that opens a dropdown the width of the entire nav bar.
Is there a good way to reactively monitor the width of that container from within the child component? I am able to get its initial width in mounted() like so:
this.parentWidth = (this.$parent.$parent.$el as HTMLElement).offsetWidth;
This only gets me the initial width of that element though. If its container grows the child component will not grow reactively. I've tried changing this assignment to a computed value but I can't access the parent element when the page is rendered and the styles computed comes back undefined or in the case below with width: 'auto' no matter what:
computed: {
styles() {
const parent = this.$parent.$parent.$el as HTMLElement;
if(!!parent) {
return {width: `${parent.offsetWidth}px`};
} else {
return {width: 'auto'};
}
}
}
Found a solution that worked here looks like there isn't an ideal solution with Vue alone. I needed to use ResizeObserver.

Getting a mousemove event from a component that doesn't want me to have it

I'm working on a React project and I'm using react-player to play a video. I want to get mousemove events while the video is playing, but react-player appears to be capturing the events and not propagating them. What can I do to get the events?
First I tried adding a handler to a div:
<div onMouseMove={() => console.log("mousemove")}>
<ReactPlayer url={props.url} />
</div>
Then I tried using addEventListener:
document.getElementById("my-div").addEventListener("mousemove", () => console.log("mousemove"), true)
<div id="my-div">
<ReactPlayer url={props.url} />
</div>
I was hoping that the addEventListener would work if I set the useCapture code to true, but it didn't help.
I couldn't find any possible way to force component to propagate events if it has internal logic preventing it.
You can try to create invisible <div> with adequate z-index attribute value to make it cover ReactPlayer component. After that you can attach listeners directly to it.
In such a way you will be able to capture all of the mouse events you want. It is not ideal way, but at least working.
Strictly speaking there is no way to provide event propagation the way you want it.
The reason is that ReactPlayer is rendered in a separate <iframe...> element that means that you are dealing with two independent DOM trees: one is in your main window context and another is in iframe context. Yet DOM events are propagated only within single DOM tree and are never being propagated outside of it.
In your specific case that means that mouse events that are happening over the player’s surface are completely processed by the code executed within iframe context and your main window will never even know about those events.
The discussed above solution with placing a kind of transparent div that would overlay the whole player’s surface won’t work either. In this case the “overlay div” would capture the mouse events indeed and these events would be properly propagated but only within the main window DOM tree. Yet DOM tree that is created within iframe would never know about these events. As you said you are aware of this already. This situation is simply opposite to the one described in the previous paragraph.
If you would have full control over the code that is run within iframe you would be able with a bit of efforts arrange events dispatching from main window to iframe using Window.postMessage() and [possibly] achieve desired results but ReactPlayer is a black box for you and this is not a working solution either.
So you have to either reject your idea of mouse event capturing or if you badly need to know that mouse pointer is moving over the players surface you have to invent other solution that is not based on [not existing] mouse event propagation between different DOM trees.
I’ve drafted a little POC component named SmartPlayer that implements one possible solution.
The basic idea is that the player is overlayed by other component named SmartOverlay that is intended to capture mouse events. This SmartOverlay is actually a grid of smaller “tiles” each of which has its own onMouseMove event handler. The trick is that once the tile’s onMouseMove event handler is fired it actually removes the tile from the DOM creating a “hole” in the overlay. Through that “hole” mouse events become “visible” to the player’s iframe. I know it sounds weird but I hope that you can get the whole picture from the code. You can actually see the “tiles” and the moving “hole” if you set “Show overlay” selector on.
Note that mouse move counter isn’t being changed while you move mouse within the “hole”. You can reduce this “granularity” by making tileHeight and tileWidth smaller but the price to be paid is a lower performance.
Strictly speaking it’s a kind of hack and I would think twice before using it in production. Though if you really need to catch the mouse event over the ReactPlayer it’s possibly the simpler solution you can get. There is some performance overhead in this solution but I tried to keep it acceptably low.
Good luck :)
P.S. I’ve had troubles making this code run as a code snippet. Hope to fix it somehow. Meanwhile I included the whole code directly in this answer.
To test the solution you may create a React application with create-react-app and then completely replace the App.js content with the code below.
I also I put the running version here: http://react-player.2358.com.ua/
import React, { Component } from 'react'
import ReactPlayer from 'react-player'
class SmartOverlay extends Component {
constructor(props) {
super(props);
const {height, width, } = props;
const tileHeight = props.tileHeight || 64;
const tileWidth = props.tileWidth || 64;
// below we're creating an array of "tiles"
// these "tiles" together are intended to cover all the players sufrace
this.overlayTiles = [];
for (let top = 0; top < height; top += tileHeight) {
for (let left = 0; left < width; left += tileWidth) {
const elementHeight = Math.min(tileHeight, height - top);
const elementWidth = Math.min(tileWidth, width - left);
const tile = {top, left, elementHeight, elementWidth }
// for each tile its own 'mousmove' event handler is created and assigned as the tile's property
tile.onMouseMove = () => this.onMouseMove(tile);
this.overlayTiles.push(tile);
}
}
// all the overlay tiles are "active" at the beginning
this.state = {activeOverlayTiles: this.overlayTiles}
}
onMouseMove(currentTile) {
// call event handler that is passed to the SmartOvelay via its handleMouseMove property
// using setTimeout here isn't strictly necessary but I prefer that "external" handler was executed only after we exit current method
setTimeout(this.props.handleMouseMove);
// "remove" current tile from the activeOverlayTiles array (that will cause removing the tile's DIV element from DOM eventually)
// actually we are not removing the item from the array but creating a new array that contains all the tiles but the current one
this.setState({activeOverlayTiles: this.overlayTiles.filter(item => item !== currentTile)})
// after current tile is removed from DOM the "hole" is left on the overlay "surface"
// and the player's iframe can "see" the mouse events that are happening within the "hole"
}
render() {
const showOverlayTileStyle = this.props.showOverlay ? {opacity: 0.5, background: '#fff', border: "1px solid #555", } : {}
return (
this.state.activeOverlayTiles.map(item => (
<div onMouseMove = {item.onMouseMove} style = {{...showOverlayTileStyle, position: 'absolute', top: item.top, left: item.left, height: item.elementHeight, width: item.elementWidth,}}></div>
))
);
}
}
const PlayerWithoutOverlay = ({height, width, url}) => (
<div style = {{position: 'absolute'}}>
<ReactPlayer height = {height} width = {width} url = {url} />
</div>
)
const SmartPlayer = ({height, width, url, showOverlay, handleMouseMove}) => (
<>
<PlayerWithoutOverlay height = {height} width = {width} url = {url} />
<SmartOverlay height = {height} width = {width} showOverlay = {showOverlay} handleMouseMove = {handleMouseMove} />
</>
)
class App extends Component {
constructor(props) {
super(props);
this.state = {
showOverlay: false,
mouseMoves: 0
}
}
toggleShowOverlay(e) {
// this simply shows/hide the ovelay depending on checkbox state
this.setState(state => ({showOverlay: !state.showOverlay}))
}
handleMouseMove(){
// adds 1 to state.mouseMoves counter
this.setState(state => ({mouseMoves: state.mouseMoves + 1}));
}
render() {
const height = 420;
const width = 640;
return (
<div style = {{margin: 12, position: 'relative'}}>
<div style = {{height: height }}>
<SmartPlayer
height = {height}
width = {width}
showOverlay = {this.state.showOverlay}
url = "https://www.youtube.com/watch?v=A0Z7fQyTb4M"
handleMouseMove = {this.handleMouseMove.bind(this)} />
</div>
<div style = {{marginTop: 12}}>
<label>Moves detected: </label>
<span>{`${this.state.mouseMoves}`}</span>
<label onClick = {this.toggleShowOverlay.bind(this)}> <input type = "checkbox" value="1" checked = {this.state.showOverlay} />Show overlay</label>
</div>
</div>
)
}
}
export default App;

How to scale Highcharts height on window resize with React

I have this example https://stackblitz.com/edit/react-sjyuej?file=Chart1.jsx
where I'm trying to make the the two charts in the left container share the height and respond to window resizing.
I've made the width responsive by setting overflow: hidden, forcing the charts to rescale as far as I understand, but I don't know how to get the same effect with the height.
Setting height='100%' in the Chart component doesn't help.
Use the highcharts-react-official package (>= 2.1) and set containerProps={{ style: { height: "100%" } }}.
This will make the chart dynamically resize to fill its parent div. You can then set up parent divs using flexbox to get your desired layout.
For example your render method would look like this:
render() {
return (
<HighchartsReact
containerProps={{ style: { height: "100%" } }}
highcharts={ Highcharts }
options={ options }
/>
);
}
Here is a live demo
Make sure you are on version >= 2.1 of highcharts-react-official (see this github issue)
After setting the height, you need to use chart.reflow() method:
componentDidMount(){
const container = this.chartComponent.current.container.current;
const table = document.getElementsByClassName('table')[0];
container.style.height = table.clientHeight + 'px';
this.chartComponent.current.chart.reflow();
}
API: https://api.highcharts.com/class-reference/Highcharts.Chart#reflow
Live demo: https://stackblitz.com/edit/react-3jwwvt?file=ChartOfficial.jsx

Changing state with body width change in React

I have to change state when I'm changing width of the document. How can I do it in React? Any suggestions how to get body element and using onChange on it?
componentDidMount() {
window.onresize = () => this.setState({ width: window.innerWidth });
}
You need first return 2 different view based on the screen size, you can get help from here:
http://www.alistapart.com/articles/responsive-web-design/
then every time the screen changes (based on values you use on each view), componentDidMount will be called and you can change the state there.

Reactjs force DOM update

I am trying to get css animations going for the height of an element via react.
CSS animations for height are hard as they require static heights.
To animate a collapse (height from x to 0) I set the height of the element to a static height (using scrollHeight) then set the height to 0 right after, and vice versa for an expand (height from 0 to x).
Unfortunately this only works sometimes. I think react is detecting that these two events are happening quickly on the same prop and only propagating the latest to the DOM which breaks my animation.
Is there a way to force react to propagate both changes to the DOM?
Code (prob not terribly interesting):
componentWillReceiveProps(nextProps){
if(nextProps.open !== this.props.open){
if(nextProps.open){
this.setState({height:0, animating: true});
}
else{
this.setState({height:this.refs.body.scrollHeight, animating: true});
}
}
}
componentDidUpdate(prevProps, prevState){
if(prevProps.open !== this.props.open){
if(prevProps.open && !this.props.open){
this.setState({height: '0'});
}
else{
this.setState({height: this.refs.body.scrollHeight});
}
setTimeout(()=>{
this.setState({animating:false});
}.bind(this), this.props.animationTime);
}
}

Categories

Resources