Is there a way to access an iframe scroll position in React? - reactjs

I tried with useRef, but i'm not receiving anything. Is there something wrong with my code or is it simply not possible to access an iframe scroll?
const iframeRef = useRef(null);
const onScroll = (e) =\> {
if (e?.target) {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if ((scrollTop + clientHeight) / scrollHeight \> 1) {
// TO SOMETHING HERE
console.log('Reached bottom');
}
}
};
<iframe
ref={iframeRef}
src={document.path}
onScroll={onScroll}
/>

Related

How to use the code above in React js using hooks

document.getElementById("cards").onmousemove = e => {
for(const card of document.getElementsByClassName("card")) {
const rect = card.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
};
}
I actually don't know how to use the above code in react js. so, if anyone knows please respond!
full source code link:
https://codepen.io/Hyperplexed/pen/MWQeYLW
to use Hook you need to handle with reference of element like this
const CardRef = React.useRef(null);
useShadow(CardRef);
return <div ref={CardRef} className="card" ></div>
And the hook would be something like this
import { useEffect } from 'react';
const useShadow = (reference: React.MutableRefObject<any>) => {
useEffect(() => {
const eventReference = (e) => {
const rect = reference.current.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
reference.current.style.setProperty('--mouse-x', `${x}px`);
reference.current.style.setProperty('--mouse-y', `${y}px`);
};
if (reference.current) {
const { current } = reference;
current.addEventListener('mousemove', eventReference);
}
return () => {
reference.current &&
reference.current.removeEventListener('mousemove', eventReference);
};
}, [reference]);
};
export default useShadow;
First of all, React does provide SyntheticEvents, so your onmousemove would probably look like this in React:
<div onMouseMove={ yourEventHandler } />
I can see what you are trying to do is to set the children .card's properties when the mouse had moved. What you can do is to have useState() in the parent .cards container to store the latest mouse position, then pass that state as props into the children. Something like:
export function Cards() {
const [mouseX, setMouseX] = useState(0);
const [mouseY, setMouseY] = useState(0);
const myOnMouseMove = (e)=> {
// set your state using setMouseX(), setMouseY()
}
return (
<div className='cards' onMouseMove={myOnMouseMove}>
<Card className='card' mouseX={mouseX} mouseY={mouseY} />
<Card className='card' mouseX={mouseX} mouseY={mouseY} />
...
</div>
)
}
(Not real implementation, just the concept)

Testing UI with conditional rendering based on screen size

I'm using react-testing-library with jest to test storybook stories. I have a component that, based on it's scrollWidth and the clientWidth it conditionally renders a button to page through the component:
function Collection({
children,
scrollOffset = DEFAULT_SCROLL_X_OFFSET,
onScroll,
...rest
}: HorizontalChipCollectionProps) {
const [scrollX, setScrollX] = useState(0); // For detecting start scroll postion
const [showRightPager, setShowRightPager] = useState(false); // For detecting end of scrolling
const scrollableAreaRef = useRef();
const scrollableAreaCallback = React.useCallback((node) => {
scrollableAreaRef.current = node;
/** Check to see if the scrollWidth is less than the full width, if so hide the right arrow */
if (node?.scrollWidth <= node?.clientWidth) {
setShowRightPager(true);
}
}, [setShowRightPager]);
const leftPageOnClick = () => pagerOnClick(PAGER_DIRECTION.LEFT);
const rightPageOnClick = () => pagerOnClick(PAGER_DIRECTION.RIGHT);
return (
<Box>
<ScrollableArea
py={{
base: 'xsmall',
medium: 'small',
}}
display="flex"
ref={scrollableAreaCallback}
onScroll={scrollCurrCheck}
overflowX="auto"
css={css`
> *:not(:last-child) {
margin-right: var(--space-small);
}
`}
{...rest}
>
{children}
</ScrollableArea>
{!showRightPager ? <Pager data-testid="right-pager" variant="right" onClick={rightPageOnClick} /> : null}
</Box>
);
}
I'm trying to test the rightPageOnClick to confirm it works but I'm unable to find the element in my test. Here is my test:
test('should show pager buttons', () => {
window.innerWidth = 1440;
window.innerHeight = 900;
window.dispatchEvent(new Event('resize'));
render(<Basic />);
const rightPager = screen.getByTestId('right-pager');
expect(rightPager).toBeInTheDocument();
});
Let me know if you need any more info from me!

React get if element scrolled to the right

I have an overflow: scroll div, and I'd like to check if it is scrolled to the right. How can I get an event listener for scroll in React and how can I calculate if it's scrolled to the right horisontally?
const elem = () => {
const [isScrolledRight, setIsScrolledRight] = useState(false)
const handleScroll = (elem: any) => {
const scrollLeft = elem.target.scrollLeft
const scrollWidth = elem.target.scrollWidth
const clientWidth = elem.target.clientWidth
setIsScrolledRight(scrollLeft + clientWidth == scrollWidth)
}
// use isScrolledRight anywhere you need
return (
<div onScroll={handleScroll.bind(this)} /> // this should be horisontally scrollable
)
}

How to ensure ref.current exists before firing on click function?

I am receiving an undefined error when trying to set canvasRef.current. I have tried many different ways to form a callback ref, but I am getting no luck. How can I wait to fire the onClick function 'handleViewStuff' AFTER canvasRef.current is not undefined?
const Child = (props) => {
const canvasRef = useRef();
const handleViewStuff = useCallback(() => {
apiCall(id)
.then((response) => {
// do stuff
return stuff;
})
.then((result) => {
result.getPage().then((page) => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d'); // error is coming in here as getContext of undefined meaning canvas is undefined'
canvas.height = 650;
const renderContext = {
canvasContext: context,
};
page.render(renderContext);
});
});
}, []);
return (
<Fragment>
<canvas ref={(e) => {canvasRef.current = e}} />
<Button
onClick={handleViewStuff}
>
View Stuff
</Button>
</Fragment>
);
};
export default Child;
Using if-statement
...
if(canvas.current) {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
}
Using optional chaining
...
const canvas = canvasRef?.current;
const context = canvas?.getContext('2d');
And I found some mistakes in your code.
add dependencies on useCallback
const handleViewStuff = useCallback(() => {
...
}, [canvasRef.current]);
should use ref like this.
<canvas ref={canvasRef} />

How can I apply a global scroll event to multiple React components?

I'm building a React app and I'd like to have a global CSS class that is used to fade in components when they appear in the viewport.
jQuery
With jQuery, I might do something like this:
const windowHeight = (window.innerHeight || document.documentElement.clientHeight);
const windowWidth = (window.innerWidth || document.documentElement.clientWidth);
isInViewport(el) {
const rect = el.getBoundingClientRect();
const vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
const horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
};
$(window).scroll(function(e) {
$('.animate').each(function() {
if(isInViewport($(this)[0])) {
$(this).addClass('animate--active');
}
});
});
On scroll, I'd check each element with the animate class and if that element is in the viewport, add the animate--active class to it, which will fade it in.
React
In React, I've moved my isInViewport() function to a global Helpers.js file so any component can make use of it, but I've had to add the scroll event and the dynamic class to every component, which makes for a lot of duplicated code. For example:
import { isInViewport } from './Helpers.js';
function MyComponent(props) {
const [inViewport, setInViewport] = useState(false);
const myComponentRef = useRef();
function handleScroll(e) {
setInViewport(isInViewport(myComponentRef.current));
}
useEffect(() => {
window.addEventListener('scroll', handleScroll);
// unmount
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
const classes = (inViewport) ? 'animate animate--active' : 'animate';
return (
<section className={classes} ref={myComponentRef}>
</section>
);
}
As far as I can tell, this would be the React way of doing this, and this does work, but again, it means that every component would require its own state variable, scroll event and class declaration, which adds up to a lot of repetition. Is there a better way of doing this?
Custom Hooks, Custom Hooks, Custom Hooks
import { isInViewport } from './Helpers.js';
function useIsInViewPort(ref) {
const [inViewport, setInViewport] = React.useState(false);
function handleScroll(e) {
setInViewport(isInViewport(ref.current));
}
React.useEffect(() => {
window.addEventListener('scroll', handleScroll);
// unmount
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return inViewport;
}
function Acmp(props) {
const ref = React.useRef();
const inViewport = useIsInViewPort(ref);
const classes = (inViewport) ? 'animate animate--active' : 'animate';
return (
<section className={classes} ref={ref}>
</section>
);
}
function Bcmp(props) {
const ref = React.useRef();
const inViewport = useIsInViewPort(ref);
return (
<section className={classes} ref={ref}>
</section>
);
}

Resources