Performance cost when using custom hooks to detect element height React - javascript

The common problem of having a position: fixed header and applying consistent padding to the content below it so that nothing is covered. If the content inside of the header is dynamically subject to changing and the styling needs to stay consistent regardless of what is added or removed.
I implemented this hook:
export const useElemHeight = () => {
const elementRef = useRef(null);
const [heightOfEl, setHeightOfEl] = useState(null);
function handleCheckElHeightOnScreenResize() {
setHeightOfEl(elementRef.current.clientHeight);
}
useEffect(() => {
if (elementRef.current) {
setHeightOfEl(elementRef.current.clientHeight);
window.addEventListener("resize", handleCheckElHeightOnScreenResize);
return () => {
window.removeEventListener("resize", handleCheckElHeightOnScreenResize);
};
}
}, [elementRef]);
return [elementRef, heightOfEl];
};
Application
export default function App() {
const [elementRef, heightOfEl] = useElemHeight();
return (
<div className="App">
<Header ref={elementRef} />
<Content height={heightOfEl} />
</div>
);
}
const Content = ({ height }) => {
const ADDITIONAL_UNITS = 20;
return (
<div
className="content"
style={{ paddingTop: `${height + ADDITIONAL_UNITS}px` }}
>
CONTENT
</div>
);
};
export default Content;
regardless of what content is added or removed from the header the padding will always stay consistent via all screen sizes, but are there any real performance costs to implementing something like this? Working demo here.
Sandbox

Related

Scroll to top when changing pages

I have a pagination component (React). I need the scroll to be moved to the top when changing pages. Can someone help me?
const MAX_BTN = 9;
const MAX_BTN_LEFT = (MAX_BTN - 1) / 2;
const Pagination = ({items, limit = 20, page, setPage}) => {
const pages = items !== null && Math.ceil(items.total_results / limit);
const startBtn = Math.max(page - MAX_BTN_LEFT, 1);
return (
<ul className="pagination">
{Array(Math.min(MAX_BTN, pages)).fill()
.map((_, i) => i + startBtn)
.map(value => (
<li key={value}>
<button onClick={() => setPage(value)}>{value}</button>
</li>
))
}
</ul>
)
}
export default Pagination;
You can return to the top with a function:
const handlePageChange = value => {
window.scrollTo(0, 0);
setPage(value);
}
And calling it in your button:
...
<li key={value}>
<button onClick={() => handlePageChange(value)}>{value}</button>
</li>
call the browser window object with below method scrollTo when you click on the Link
window.scrollTo(0, 0);
An elegant way to handle this is by creating a single ref for the top-most part of your app. Then pass the ref into a hook which handles scrolling to the top of the page when the pathname changes.
Here's a simple hook I use (type definitions can be cleaned up):
useScrollToTop.ts
import { useEffect } from 'react';
import { useLocation } from 'react-router';
const useScrollToTop = (ref: any) => {
const location = useLocation();
// ensure pages scroll to top
useEffect(() => {
if (ref?.current) {
ref.current.scrollIntoView();
}
}, [ref?.current, location.pathname]);
return null;
};
export default useScrollToTop;
Now at the top of your app's very first element add the reference.
App.js: (or whatever component)
import useScrollToTop from './useScrollToTop';
Then create a new empty ref:
const ref = useRef<HTMLDivElement | null>(null);
Pass the ref into the hook:
useScrollToTop(ref)
Define the ref on the top-most part of your app (example inner components):
return (
<div ref={ref}>
<Header />
<Navigation />
<Content />
<Footer ?>
</div>
);
Now anytime the path changes, your end-user will scroll to the top of the page.

How can I synchronize the "scrollLeft" property of 2 divs while scrolling horizontally within a react component?

I would like to "synchronize" the x-position of 2 "divs" within a react component. Eventually, I would like to have a table header that stays always visible and a table that can scroll vertically. The horizontal offset of that table header and that table are supposed to remain "in sync".
The "onScroll" event fires. However, changing the "state" of the property "offsetX" in my function reactToScrolling has no effect on my "divs" (as far as I can see). What can I do to make this work?
const { useState } = require('react');
const MainComponent = () => {
const [ offsetX, setOffsetX ] = useState(0);
function reactToScrolling(e) {
console.log(e.target.scrollLeft);
setOffsetX(e.target.scrollLeft);
}
return (
<>
<div style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={reactToScrolling}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'red' }} scrollLeft={offsetX}>
...
</div>
</div>
<div style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={reactToScrolling}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'blue' }} scrollLeft={offsetX}>
...
</div>
</div>
</>
)
};
export default MainComponent;
Eventually, I found the solution myself. It works perfectly if useRef is used instead of useState. When the scroll event fires, the scrollLeft property of div1 is set to the value of the scrollLeft property of div2 using the references created with useRef.
const { useRef } = require('react');
const MainComponent = () => {
const div1 = useRef(null);
const div2 = useRef(null);
const onScroll = () => {
div1.current.scrollLeft = div2.current.scrollLeft;
}
return (
<>
<div ref={div1} style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={onScroll}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'lightgray' }}>
...
</div>
</div>
<div ref={div2} style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={onScroll}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'lightgray' }}>
...
</div>
</div>
</>
)
};
export default MainComponent;
Thanks, this helped me a lot.
For a final touch, I just added another function for the top div so that it can handle the scrolling itself too.
const onScrollTop = () => {
div2.current.scrollLeft = div1.current.scrollLeft;
}

ReactJs Functional Component Get When Scroll Reached End

I have a functional component in ReactJs and want to add pagination to my page so that when the user reaches end of the screen, I'd get the rest of the data from Api.
my code looks like this:
<div
className="MyOrdersHistory-list-container">
{ordersList.map((order, index) => (
<div key={index} className="MyOrdersHistory-listItem-container">
........
</div>
))}
</div>
How can I figure out when use scrolls to the end of the page to fire the Api request. (If possible please give a functional component example not a class one)
Thanks
import React, { useRef, useEffect } from 'react';
const <YourComponent> = () => {
const list = useRef();
const onScroll = () => {
if (list.current) {
const { scrollTop, scrollHeight, clientHeight } = list.current;
if (scrollTop + clientHeight === scrollHeight) {
// DO SOMETHING WHAT YOU WANT
}
}
};
...
<div
onScroll={() => onScroll()} ref={list}
className="MyOrdersHistory-list-container">
{ordersList.map((order, index) => (
<div key={index} className="MyOrdersHistory-listItem-container">
........
</div>
))}
</div>

Tooltip positioning in table cell

I'm using React and have a table with some actions (delete, edit, etc.) in the cell. And I need to put a tooltip on each action. I'm not using the jquery and don't plan to, and not title props (I will need to upgrade this tooltip to some specific data or even another component).
So the problem is I can't position the tooltip correctly (for example in the middle of the top or bottom). Witch params should my component receive and how to do it with css?
const Tooltip = ({position = 'top', display, style, children}) => {
let displayClass = display ? `fade ${position} in` : `tooltip-${position}`
return (
<div className={`tooltip ${displayClass} `} role='tooltip'>
<div className='tooltip-arrow' />
<div className='tooltip-inner'>
{children}
</div>
</div>
)
}
const ActionLinkItem = ({page, action, data, onMouseEnter, onMouseLeave, display, tooltipText, id}) => {
const {buttonClass, icon} = actionsStyles[action]
return (
<Link to={`/${page}/${action.toLowerCase()}/${data.id}`}>
<a
className={`btn btn-xs ${buttonClass}`}
id={id}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
><i className={`fa fa-${icon}`} />
<Tooltip display={display} action={action}>{tooltipText}</Tooltip>
</a>
</Link>
)
}
export default class Actions extends Component {
constructor (props) {
super(props)
this.state = {
tooltipActive: ''
}
}
handleHover (event) {
this.setState({
tooltipActive: event.target.id
})
}
handleBlur (event) {
this.setState({
tooltipActive: ''
})
}
getActionsTemplate () {
const {actions, data, page} = this.props
return actions.map(action => {
let display = this.state.tooltipActive === `${action.action}-${data.id}`
let id = `${action.action}-${data.id}`
let tooltip = tooltipText[action.action].replace(/{type}/g, page).replace(/{item}/g, data.name
return <ActionLinkItem
key={`${data.id}-${action.action}`}
page={page}
action={action.action}
data={data}
id={id}
tooltipText={tooltip}
display={display}
onMouseEnter={(e) => this.handleHover(e)}
onMouseLeave={(e) => this.handleBlur(e)}
/>
})
}
render () {
return (
<div className='row'>
{this.getActionsTemplate()}
</div>
)
}
}
At its simplest, tooltip should be absolutely positioned within a positioned element.
So, add a style of position: relative to the in ActionLinkItem
and add a style of `position: absolute to the outer in Tooltip.
For added credit, you will want to set a width on your tooltip, and position or center it within the of ActionLinkItem using styles like bottom: 100%.
You can also do some calculations to ensure that your tooltip does not run off the page by moving it left and right if the containing is on the right or left respectively.

In react, how to get noticed when children change?

I am making this class called Scrollable which enables scrolling if the width/height of the children elements exceeds a certain value. Here is the code.
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
render() {
let outter_styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={outter_styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
// To use: <Scrollable y><OtherComponent /></Scrollable>
This works great. Except now I wish to add one more functionality which makes the scrollable always scroll to the bottom. I have some idea of how to do it:
this.outterEl.scrollTop = this.innerEl.offsetHeight;
But this only need to be called when this.props.children height changes. Is there any idea on how to achieve this?
Thanks in advance.
I would recommend a package element-resize-detector. It is not React-specific but you can easily build a high-order component around it or integrate your Scrollable component with it.
Now I have an idea of solving this.
Since I am using react-redux. The problem is that I could not use lifecycle hooks on this Scrollable component since this.props.children might not necessarily be changed when the content is updated.
One way to achieve this is to make Scroll component aware of the corresponding values in the redux state. So that when that relevant value is updated, we can scroll down to the bottom.
Scrollable component:
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
componentWillUpdate(){
if(this.props.autoScroll){
// only auto scroll when the scroll is already at bottom.
this.autoScroll = this.outterEl.scrollHeight - this.outterEl.scrollTop - Number.parseInt(this.props.height) < 1;
}
}
componentDidUpdate(){
if(this.autoScroll) this.outterEl.scrollTop = this.outterEl.scrollHeight;
}
render() {
let styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
Scrollable container:
import { connect } from 'react-redux';
import Scrollable from '../components/Scrollable';
const mapStateToProps = (state, ownProps) => Object.assign({
state: state[ownProps.autoScroll] || false
}, ownProps);
export default connect(mapStateToProps)(Scrollable)
With this, Scrollable's life cycle hooks will be called when the corresponding state changes.

Categories

Resources