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.
Related
I have a parent div card which can display one of three child divs. I want to adjust the height of the parent div to fit the exact contents of the currently-visible child div.
To do this, I create a updateDonationCardSize function which reads the refs of all three views, and depending on which one should be currently visible, sets the parent div height to match the appropriate child div height.
function updateDonationCardSize() { // Sets the size of the card to fit contents
const height = stepOneContainerRef.current.clientHeight
setDonationCardSize(prevState => ({
...prevState,
['width']: paymentFooterRef.current.clientWidth,
['height']: height
}))
}
I call this function whenever any of the refs pointing to one of the subviews updates.
useEffect(() => {
updateDonationCardSize()
setTimeout(() => updateDonationCardSize(), 700)
}, [stepOneContainerRef, stepTwoContainerRef, stepThreeContainerRef])
Notice the silly approach above where the function is called twice, once immediately and once after a brief delay. For some reason, the first time it runs, it returns the wrong height of 830 whereas the second time it runs, it returns the correct height of 700.
What's even stranger is that if I print out height variable as well as the stepOneContainerRef object into the console next to each other, the height variable is returned as 830 while stepOneContainerRef object actually displays its current.clientWidth property as 700...
I am completely clueless and have spent hours over this already.
Edit - Providing additional information
The parent div contains the following structure:
<div id='parent div' className='overflow-hidden'>
<div id='parent of dynamic content'>
<div id='containerOne' className='relative' style={stepOneContainerStyle} />
<div id='containerTwo' className='relative' style={stepTwoContainerStyle} />
<div id='containerThree' className='relative' style={stepThreeContainerStyle} />
</div>
<div id='footer' />
</div>
and the stepXContainerStyle is a dictionary which sets position: top and x to 0 if it the particular div should be currently visible, otherwise it moves it outside of the overflow region to hide it.
The different heights are result of unfinished DOM mutations during the rendering of the components. To fix that change useEffect hook to useLayoutEffect which fires synchronously after all DOM mutations. It will allow you to call updateDonationCardSize only once.
In addition, you should wrap updateDonationCardSize with useCallback for correct hook dependency specification.
const updateDonationCardSize = useCallback(() => { // Sets the size of the card to fit contents
setDonationCardSize(prevState => ({
...prevState,
['width']: paymentFooterRef.current.clientWidth,
['height']: stepOneContainerRef.current.clientHeight
}))
}, [paymentFooterRef, stepOneContainerRef]);
useLayoutEffect(() => {
updateDonationCardSize()
}, [updateDonationCardSize])
I'd also recommand to try solve this problem with CSS rules - it looks like a CSS problem more than component rendering problem.
Using AJAX request, I am trying to change background image and set it's style properties. In the function which is called every few seconds through setInterval method, I define style and set states of component in the following way:
changeThings() {
let maxVal = this.props.data.length;
let ranNum = Math.floor((Math.random() * maxVal));
let imgVal = (this.props.url[ranNum])? 'url('+ this.props.url[ranNum].url+')':null;
let style = {
background:imgVal,
backgroundSize:'cover',
backgroundPosition:'center',
bacgkrouhndRepeat:'no-repeate'
};
this.setState({content:{style:style,
section:section,
title:title,
by:author,
dateInfo:updatedDate}});
}
render() {
return ({
<div>
//...other components
(this.state.content.style)?
<div id="image" style={this.state.content.style}>:null
//...other components
</div>
})
}
The very first image is displayed with every background image css properties applied. From second images however, it only changes images(background:url value) but not other background image properties such as position, repeat, size etc.
What is the reason of this problems and How can I solve it?
Here is how I fixed the problem. First I only saved URL string data to this.state.content rather than entire css style. It worked when I set the style property directly to the JSX.
Also, basically when DOM define the value for style property of background-size, it calculates the value based on the current 'snapshot' value of element height and width where the image will be display. I am currently making responsive web app so I thought the css style does not work when I changed the view.
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.
I am currently trying to implement React Virtualized to replace an laggy table but am running into an issue. I am using WindowScroller, AutoSizer, Table, and Column from React Virtualized,
In my 400 row table, about 30 rows appear before they stop showing up (as in those DOM elements have not been rendered). However, the table appears to be the correct height. Here is a picture to help visualize:
From what I can tell, the culprit is (or related to) autoHeight on the <Table /> element. When I remove it, I can scroll through all the row elements within the Table. However, this breaks the desired functionality of being able to scroll the page, not the Table.
Things I have tested so far:
It occurred to me that the issue might be with the scrollElement on WindowScroller since the container element for my table has overflow: scroll; height: 100vh. When I tried setting the scrollElement property to this element none of my rows would render. For testing purposes, I also tried removing this container so that window would handle scrolling, but this didn't solve the bug either.
I have tried replicating this example as closely as possible, but no dice so far.
Finally, here is a simplified code snippet:
import React, { Component } from "react";
import { Table, Column, WindowScroller, AutoSizer } from "react-virtualized";
import "react-virtualized/styles.css";
import "./style.scss";
class AnalyticsResponsesReportTable extends React.Component {
constructor(props) {
...
}
//Methods...
render() {
const data = this.props.values.data;
return (
<div className="AnalyticsResponsesReportTable">
{data.length && (
<WindowScroller>
{({
height,
isScrolling,
registerChild,
onChildScroll,
scrollTop
}) => (
<div>
<AutoSizer disableHeight>
{({ width }) => (
<Table
ref="Table"
headerHeight={40}
height={height}
width={width}
autoHeight
rowCount={data.length}
rowHeight={40}
rowGetter={({ index }) => data[index]}
className="AnalyticsResponsesReportTable__table"
onRowClick={this.handleRowClick}
>
//Columns rendering here
</Table>
)}
</AutoSizer>
</div>
)}
</WindowScroller>
)}
</div>
);
}
}
export default AnalyticsResponsesReportTable;
After trying out many different combinations, the root issue was that I had not set scrollTop={scrollTop} on Table. I also needed to set scrollElement to the appropriate container. The issue took so long to solve since both are needed, and I guess I had just tested one without the other
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} />