What does the .current property of the querySelector method refer to? - javascript

I came across this line of code via a snippet on https://usehooks.com,
document.querySelector('body').current
I haven't been able to find .current in the specification at all.
I was hoping someone could clarify its purpose in this context.
It's being used within the IntersectionObserver API in the full example (below) - perhaps the API is exposing the property?
Any help is much appreciated. Thanks in advance.
Following is the full source code:
import { useState, useEffect, useRef } from 'react';
// Usage
function App() {
// Ref for the element that we want to detect whether on screen
const ref = useRef();
// Call the hook passing in ref and root margin
// In this case it would only be considered onScreen if more ...
// ... than 300px of element is visible.
const onScreen = useOnScreen(ref, '-300px');
return (
<div>
<div style={{ height: '100vh' }}>
<h1>Scroll down to next section 👇</h1>
</div>
<div
ref={ref}
style={{
height: '100vh',
backgroundColor: onScreen ? '#23cebd' : '#efefef'
}}
>
{onScreen ? (
<div>
<h1>Hey I'm on the screen</h1>
<img src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif" />
</div>
) : (
<h1>Scroll down 300px from the top of this section 👇</h1>
)}
</div>
</div>
);
}
// Hook
function useOnScreen(ref, margin = '0px') {
// State and setter for storing whether element is visible
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
// Update our state when observer callback fires
setIntersecting(entry.isIntersecting);
},
{
rootMargin: margin,
root: document.querySelector('body').current
}
);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
observer.unobserve(ref.current);
};
}, []); // Empty array ensures that effect is only run on mount and unmount
return isIntersecting;
}

document.querySelector('body').current is just a property of the body element, which has nothing to do with document.querySelector. It may have been set somewhere else as it is not an existing property of the body element.
var body = document.querySelector("body");
console.log("body.current:", "body.current");
body.current = "SOMEVALUE";
console.log("After setting body.current");
console.log("body.current:", "body.current");

Sorry to disappoint, but it doesn't do anything. It's just a way to supply undefined to the IntersectionObserver API. If you replace document.querySelector('body').current with undefined or remove the entire root field altogether, you still get the same result.
I removed that field to test it to verify the same behavior. Try it yourself in the Codesandbox link here.
As seen by this comment on the example, it can be removed entirely:
You can remove root entirely, since it defaults to the viewport anyway (also document.querySelector('body').current is always undefined, could be document.body but isn't needed anyway)

Related

React scroll event keeps on firing

I'm working on a react functional components project, wherein I've to increment scroll position programmatically when a user actually scrolls in a div.
for example, I've a div with id testDiv & a user scrolled down a bit, now the scroll position is 100, so I want to programmatically increment it by 1 and make it 101.
Problem statement: The scroll position keeps on incrementing via onScroll handler, so the scrollbar only stops at the end of the element even if we scroll only once.
Expected behaviour: Scroll position should be incremented only once by the onScroll handler if we scroll once on the UI.
What I tried: (dummy code for the reproduction purpose)
import React, { useCallback } from "react";
import ReactDOM from "react-dom";
const App = (props) => {
const onScroll = useCallback((event) => {
const section = document.querySelector('#testDiv');
// **The problem is here, scrollTop keeps on incrementing even if we scrolled only once**
if (section) section.scrollTop = event.scrollTop + 1;
}, []);
return (
<div id="testDiv" onScroll={onScroll} style={{ height: "500px", overflowY: "scroll" }}>
<div>test</div>
<div className="forceOverflow" style={{height: 500 * 25}}></div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
What you're looking for is throttling or debouncing the function. It keeps on incrementing because with every bit of scroll, onScroll is being called.
There are many ways to throttle functions in react but I like to use debounce from lodash. If I remember correctly it was about 1.5kb gzipped.
I made you a sandbox. And here is the code:
import React, { useCallback } from "react";
import _debounce from "lodash/debounce";
export const App = (props) => {
let debouncedOnScroll = useCallback(
_debounce(() => {
// YOUR CODE HERE
console.log("CHECK THE CONSOLE TO SEE HOW MANY TIMES IT'S GETTING CALLED");
}, 150), []); // Wait 150ms to see if I'm being called again, if I got called before 150ms, don't run me!
return (
<div
id="testDiv"
onScroll={debouncedOnScroll}
style={{ height: "500px", overflowY: "scroll" }}
>
<div>test</div>
<div className="forceOverflow" style={{ height: 500 * 25 }}></div>
</div>
);
};
By the way, use useRef API instead of document.querySelector. This query selector is getting called with every scroll and it's not the lightest weight on the client computer.

Get getAttribute from button and toggle class name to body react hooks

i want to improve my code, with several buttons that has custom class names (attr), when clicked should add to body tag (toggle), now is adding the first button only because for ("button")[0] but should work for each button
import React, { useState, useEffect } from "react"
function Test() {
const [isClass, setIsClass] = useState(false)
useEffect(() => {
const x = document.getElementsByTagName("button")[0].getAttribute("custom-class")
document.body.classList.toggle(x, isClass)
}, [isClass])
return (
<>
<button custom-class='test1' onClick={() => setIsClass(!isClass)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => setIsClass(!isClass)}>
Setting test2 className
</button>
</>
)
}
export default Test
Thanks
Please use this code.
let oldStyle = "";
const handleClick = (index) => {
const x = [...document.getElementsByTagName("button")].map(value => value.getAttribute("custom-class"));
document.body.classList.contains(x[index]) ? document.body.classList.remove(x[index]) : document.body.classList.add(x[index]);
if(document.body.classList.length > 1) document.body.classList.replace(oldStyle, x[index]);
oldStyle = x[index];
}
return (
<>
<button custom-class='test1' onClick={() => handleClick(0)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => handleClick(1)}>
Setting test2 className
</button>
</>
)
It is better not to use DOM querying and manipulation directly with elements that are created and controlled by react. In your particular example it is ok to use document.body, but not ok to search for buttons, especially when you try to find them by tag name. To actually toggle a class in classList you don't need second parameter in most cases, so additional state is also not needed.
React way to get reference to element renderend by React would be to use Ref. However, in your particular case side effect can be launched inside event handler, so you don't need useEffect or useRef.
Your onClick handler can accept event object that is Synthetic Event. It holds property target that holds reference to your button.
So, the easiest way would be simply to write like this:
function Test() {
function clickHandler(event) {
let classToToggle = event.target.getAttribute("custom-class");
document.body.classList.toggle(classToToggle);
}
return (
<>
<button key="test1" custom-class="test1" onClick={clickHandler}>
Setting test1 className
</button>
<button key="test2" custom-class="test2" onClick={clickHandler}>
Setting test2 className
</button>
</>
);
}
export default Test;
If you need to have only single className from the list, you can decide which class to enable or disable with a bit of a state. Since anything can add classes on body it might be useful to operate only on some set of classes and not remove everything.
Also, not mentioned before, but consider using data attribute as its purpose is to keep some additional data.
function Test() {
// this can come from props or be hardcoded depending on your requirements
// If you intend to change it in runtime, consider adding side effect to cleanup previous classes on body
let [classesList] = React.useState(["test1", "test2"]);
let [activeClass, setActiveClass] = React.useState("");
// You can switch actual classes in effect, if you want to
function clickHandler(event) {
let classToToggle = event.target.dataset.customClass;
// we remove all classes from body that are in our list
document.body.classList.remove(...classesList);
if (activeClass === classToToggle) {
setActiveClass("");
} else {
// if class not active - set new one
document.body.classList.add(classToToggle);
setActiveClass(classToToggle);
}
}
return (
<>
{classesList.map((cn) => (
<button key="cn" data-custom-class={cn} onClick={clickHandler}>
Setting {cn} className
</button>
))}
</>
);
}

React - Cannot get the actual value of picture height/width

I'm making a Face Recognition React web app following a Udemy course, however the course materiel is outdated so I decided to take control into my hands and rebuilt it using hooks and context API.
The problem - I cannot get the actual height and width of the uploaded (fetched) image. Tried many different approaches but cannot make it work.
Sometimes when the picture is uploaded I'm not getting anything back for it's width and height, sometimes the values are not being updated in "useState". I need those values to be correct so the calculations in the future can me made for detecting the face from the image.
A quick rundown of what is happening here.
"useEffect" is being used for immediately setting up "img" state with it's current height and width properties => in JSX part the "" source is being fetched from my context API which is being fetched from "ImageLinkForm" component.
const ImageField = () => {
const faceContext = useContext(FaceContext);
const ref = useRef();
const [img, setImg] = useState({
height: '',
width: ''
});
useEffect(() => {
setImg({ ...img, height: ref.current.clientHeight, width: ref.current.clientWidth })
console.log(`This is height ${img.height}`);
console.log(`This is width ${img.width}`);
}, [faceContext]);
return (
<div className="p-3">
<div className="fieldImg">
<img src={faceContext.fieldUrl} class="img-fluid rounded-lg" id="inputImage" ref={ref} alt="Responsive image" />
<div><h4 className="text-primary">HEADER {img.height}</h4></div>
</div>
</div>
)
}
This on the paper looked like such an easy problem but I've been stuck on this for weeks.
If anyone is willing to have a look from inside here's the github repo - https://github.com/Fruscoqq/FaceRecognition
Any help would be highly appreciated.
You can remove the entire hook as shown below, given that you have encountered that it does not work correctly.
useEffect(() => {
setImg({ ...img, height: ref.current.clientHeight, width: ref.current.clientWidth })
console.log(`This is height ${img.height}`);
console.log(`This is width ${img.width}`);
}, [faceContext]);
Instead of passing the ref variable as such ref={ref}, pass in a ref callback func instead.
ref={onRefChange}
Inside the func, you can update the img state whenever the DOM element changes.
const onRefChange = useCallback(node => {
// ref value changed to node
if (node !== null && node.clientHeight !== null && node.clientWidth !== null) {
setImg({ ...img, height: node.clientHeight, width: node.clientWidth })
}
}, [img]);
The reason why we are using calback refs is because React recommends it to detect changes in ref value.

How do I style a div inside a component without passing props to that component (I'm using a package)

I'm using the react-scrollbar package to render a scrollbar for my my content. What I also want is a arrow button that, on click, moves to a certain scrollbar area. The problem is, I'm trying to style (marginTop) a class inside my component.
This is my attempt:
// MY COMPONENT
scrollToNextUpload = () => {
const NextUpload = 400
this.setState({ marginTop : this.state.marginTop + NextUpload }, () => document.getElementsByClassName('scrollarea-content')[0].style.marginTop = "'" + this.state.marginTop + "px'")
}
// MY RENDER
render () {
<ScrollArea>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
What is actually rendered
<div class='scrollarea'>
<div class='scrollarea-content'>
// my content
<div onClick={this.scrollToNext}></div>
</div>
</div>
What I want
To make my area with the scrollbar scroll, I have to add a marginTop style to the 'scrollarea-content'. I could do this by passing props to the < ScrollArea > and then use them inside the installed package; but I'm trying to avoid altering the original package content.Also, is there another way how I could scroll by click and is there someone else who's experienced with that NPM Package?
Most libraries give props to apply style to child components, in this library you can pass a className to the contentClassName or use inline style in contentStyle prop :
<ScrollArea contentStyle={{ marginTop: 10 }}>
An another way is to write css to add style to the scrollarea-content class.
In a .css file :
.scrollarea-content {
margin-top: 10px;
}
Edit: In your case you can programatically change the marginTop style by using the props like this :
scrollToNextUpload = () => {
const NextUpload = 400;
this.setState(prevState => ({ marginTop : prevState.marginTop + NextUpload }));
}
render () {
<ScrollArea contentStyle={{ marginTop: this.state.marginTop }}>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
Note the use of a functional setState to prevent inconsistencies when next state value depends on the previous state.

How to detect hidden component in React

In brief,
I have a infinite scroll list who render for each Item 5 PureComponent.
My idea is to somehow, only render the 5 PureComponent if the Item is visible.
The question is,
How to detect if the Item component is visible for the user or not?
Easiest solution:
add scrollPosition and containerSize to this.state
create ref to container in render()
<div ref={cont => { this.scrollContainer = cont; }} />
in componentDidMount() subscribe to scroll event
this.scrollContainer.addEventListener('scroll', this.handleScroll)
in componentWillUnmount() unsubscribe
this.scrollContainer.removeEventListener('scroll', this.handleScroll)
your handleScroll should look sth like
handleScroll (e) {
const { target: { scrollTop, clientHeight } } = e;
this.setState(state => ({...state, scrollPosition: scrollTop, containerSize: clientHeight}))
}
and then in your render function just check which element should be displayed and render correct ones numOfElementsToRender = state.containerSize / elementSize and firstElementIndex = state.scrollPosition / elementSize - 1
when you have all this just render your list of elements and apply filter base on element's index or however you want to sort them
Ofc you need to handle all edge cases and add bufor for smooth scrolling (20% of height should be fine)
You can use the IntersectionObserver API with a polyfill (it's chrome 61+) . It's a more performant way (in new browsers) to look for intersections, and in other cases, it falls back to piro's answer. They also let you specify a threshold at which the intersection becomes true. Check this out:
https://github.com/researchgate/react-intersection-observer
import React from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '#researchgate/react-intersection-observer';
class ExampleComponent extends React.Component {
handleIntersection(event) {
console.log(event.isIntersecting); // true if it gets cut off
}
render() {
const options = {
onChange: this.handleIntersection,
root: "#scrolling-container",
rootMargin: "0% 0% -25%"
};
return (
<div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
<Observer {...options}>
<div>
I am the target element
</div>
</Observer>
</div>
);
}
}

Categories