I'm using the useHover() react hook defined in this recipe. The hook returns a ref and a boolean indicating whether the user is currently hovering over element identified by this ref. It can be used like this...
function App() {
const [hoverRef, isHovered] = useHover();
return (
<div ref={hoverRef}>
{isHovered ? 'Hovering' : 'Not Hovering'}
</div>
);
}
Now let's say that I want to use another (hypothetical) hook called useDrag which returns a ref and a boolean indicating whether the user is dragging the current element around the page. I want to use this on the same element as before like this...
function App() {
const [hoverRef, isHovered] = useHover();
const [dragRef, isDragging] = useDrag();
return (
<div ref={[hoverRef, dragRef]}>
{isHovered ? 'Hovering' : 'Not Hovering'}
{isDragging ? 'Dragging' : 'Not Dragging'}
</div>
);
}
This won't work because the ref prop can only accept a single reference object, not a list like in the example above.
How can I approach this problem so I can use multiple hooks like this on the same element? I found a package that looks like it might be what I'm looking for, but I'm not sure if I'm missing something.
A simple way to go about this is documented below.
Note: the ref attribute on elements takes a function and this function is later called with the element or node when available.
function App() {
const myRef = useRef(null);
return (
<div ref={myRef}>
</div>
);
}
Hence, myRef above is a function with definition
function(element){
// something done here
}
So a simple solution is like below
function App() {
const myRef = useRef(null);
const anotherRef = useRef(null);
return (
<div ref={(el)=> {myRef(el); anotherRef(el);}}>
</div>
);
}
A React ref is really nothing but a container for some mutable data, stored as the current property. See the React docs for more details.
{
current: ... // my ref content
}
Considering this, you should be able to sort this out by hand:
function App() {
const myRef = useRef(null);
const [hoverRef, isHovered] = useHover();
const [dragRef, isDragging] = useDrag();
useEffect(function() {
hoverRef.current = myRef.current;
dragRef.current = myRef.current;
}, [myRef.current]);
return (
<div ref={myRef}>
{isHovered ? 'Hovering' : 'Not Hovering'}
{isDragging ? 'Dragging' : 'Not Dragging'}
</div>
);
}
Related
I am experienced in working with Javascript, but am pretty new to working with React. When coding purely in javascript that would be rendered in browser, I would often use:
width = document.getElementById('element_id').clientWidth
to dynamically size my svg elements to different container sizes. Does something like this exist in React? I have been trying to use the same technique, but as expected, it does not work because the the template is rendered after the script is run.
Use can use this it work.
useEffect(didUpdate);. Accepts a function that contains imperative, possibly effectful code.
const App = () => {
useEffect(() => {
const width = document.getElementById('width').clientWidth;
console.log({ width });
}, []);
return(
<div id="width" />
);
}
Of course. It is very easy thing to do using "createRef" hook.
Basically, document.getElementById search for a DOM element and with createRef you are assigning a reference to (virtual)DOM element and you can do every manipulation on it.
Check how it's done here:
import React, { createRef } from 'react';
export const FooComponent = () => {
const fooRef = createRef<HTMLDivElement>();
const handleClick = () => {
const divClientWidth = fooRef.current?.clientWidth;
window.alert(divClientWidth);
};
return (
<div ref={fooRef}>
<button onClick={handleClick}>Show client width</button>
</div>
);
};
I want to start out by telling you that I am new to react and I come from a background of working almost entirely with vanilla JS and HTML.
I want to know how to reference/pass an element as an argument in a function similar to how you would go about it in JS:
const myElement = document.getElementById("my-element");
or
const myElement = document.createElement("div");
function testFunc(element) {
return element.getBoundingClientRect();
}
testFunc(myElement);
I have googled a bit but haven't found any good answers, all I could find was about "ref" but I am sure there is a simpler solution.
This is what I have in React now:
import React from "react";
import "./myComp.css";
function myComp(props) {
const sliderContainer = (
<div className="slider-container" style={props.styles}></div>
);
function myFunc(element) {
return element.getBoundingClientRect();
}
const elementDimensions = myFunc(sliderContainer);
return { sliderContainer };
}
export default myComp;
But what i want to do is:
import React from "react";
import "./myComp.css";
function myComp(props) {
const sliderContainer = "slider-container" //SOME SORT OF REFRENCE / SELECTOR, I'VE FIGURED OUT THAT querySelectors IS NOT THE RIGHT APPORACH
function myFunc(element) {
return element.getBoundingClientRect();
}
const elementDimensions = myFunc(sliderContainer);
return (
<div className="slider-container" style={props.styles}>
<div className="myChild"></div>
</div>
);
}
export default myComp;
This should work:
const sliderContainer = useRef(null);
function myFunc(element) {
return element.getBoundingClientRect();
}
useEffect(() => {
const elementDimensions = myFunc(sliderContainer.current);
});
return (
<div ref={sliderContainer} className="slider-container" style={props.styles}>
<div className="myChild"></div>
</div>
);
The ref property will assign the element to sliderContainer.current once the component has been mounted.
Note that the value is initially set to be null: useRef(null).
You must wait until the element is not null before accessing it as the element will not yet be mounted when the component is initially rendered.
in React you can use
document.getElementById('id-object');
or
document.querySelector('object-class');
But you can find more info in this article on Medium.
Hope that this was useful!
You can use document.getElementById('slider-container'); or any other use of document in React.
I've red many articles about using useRef in react js. According to react js documentation, Refs provide a way to access DOM nodes or React elements created in the render method..
From all of documentation, i understood that, if you want to access a dom element, you have to add the ref like this:
<div ref={myRef}, and after that easy to access it.
I understood that useRef is used when we have access to the html markup.
Question: How to access a css selector (.class, #id), when my html is generated by a library, like
AntD or something else?
How to access this selector if i can't use document.querySelector according to react documentation? EX:
document.querySelector('my selector').style.color = 'yellow';
Which is the alternative for the last code snippet in react js?
NOTE: I don't want to change the styles with css, but i need to change it according to some js logic.
You can use querySelector and querySelectorAll in situations where the DOM structure you're accessing is generated outside of React. That's absolutely fine when you have no other choice.
You'd probably use them on the outermost element of your component or the element in which you're having the non-React library do its thing (which you'd get via a ref), rather than on document, so that they're working just within your component's part of the DOM rather than the entire document.
Here's an example:
"use strict";
const { useState, useEffect, useRef } = React;
// A stand-in for your non-React library
function nonReactLibraryFunction(element, value) {
element.innerHTML =
`<div>
This is content from the non-React lib, value =
<span class="value">${value}</span>
</div>`;
}
// A stand-in for your component
const Example = ({value}) => {
// The ref for the wrapper around the lib's stuff
const fooRef = useRef(null);
// When `value` changes, have the library do something (this
// is just an example)
useEffect(() => {
// Valid target?
if (fooRef.current) {
// Yes, let the lib do its thing
nonReactLibraryFunction(fooRef.current, value);
// Find the element we want to change and change it
const color = value % 2 === 0 ? "blue" : "red";
fooRef.current.querySelector(".value").style.color = color;
}
}, [value]);
return (
<div>
This is my component, value = {value}.
<div ref={fooRef} />
</div>
);
};
// A wrapper app that just counts upward
const App = () => {
const [value, setValue] = useState(0);
useEffect(() => {
setTimeout(() => setValue(v => v + 1), 800);
}, [value]);
return <Example value={value} />;
};
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.development.js"></script>
If you use external libraries that generate their own markup. Nothing bad to use element.querySelector
You can do something like this:
React.useEffect(() => {
const element = document.querySelector(selector);
}, []);
But if you use a library from React's ecosystem like Antd or material-ui, they probably have an API with refs. It can be called like innerRef, nestedRef or just ref
In addition to previous anwsers: if this generated html is generated inside your component you can use querySelector on useRef.current to search for element only inside component ;)
const Component = () => {
const componentRef = useRef(null);
useEffect(() => {
const elFromLibraryComponent = componentRef.current.querySelector('my selector');
if (elFromLibraryComponent) {
...do something...
}
}, [])
return (
<div ref={componentRef}>
<ComponentFromLibrary />
</div>
)
}
There are even libraries that allows to pass ref as prop to components so it is also usefull
If you are using multiple external resources to generate markup. Try this:
useEffect(() => {
const element = window.parent.document.querySelector(selector);
},[]);
We can also manipulate HTML Dom element by tags,id,class with the help of document.getElementsByTag('TagName').Document.getElementById('IdName'),Document.getElementsByClass('ClassName').
I have code where if a function is invoked it will call toggleCheckedUser and pass along information about which object property to toggle. Then saves the modified object back to state (selectedSendTo).
However, when I run this, the toggle it works, but when I try to edit a second property, before changing it I try console.log(selectedSendTo) I always get the initial value whether it be an empty object {} or false instead of the previously updated object.
When I use useEffect to spy on selectedSendTo I can see that the setSelectedSendTo() function correctly updated the object. So for some reason when I revisit the object it's empty.
const [selectedSendTo, setSelectedSendTo] = useState(false);
const toggleCheckedUser = (companyID, contactID) => {
console.log(companyID, contactID);
console.log(selectedSendTo); // THIS VALUE IS always the same as INITIAL value
console.log(selectedSendTo[companyID]);
if(selectedSendTo[companyID] &&
selectedSendTo[companyID][contactID] === true){
//remove it
delete(selectedSendTo[companyID][contactID]);
}else{
setSelectedSendTo({
...selectedSendTo,
[companyID]:{
...selectedSendTo[companyID],
[contactID]: true,
}
})
}
}
Here is the DOM:
<CustomCheckbox
className="person__checkbox" name={`checkbox-${contactID}`}
alreadySelected={
selectedSendTo[company.companyID] &&
selectedSendTo[company.companyID][contactID]
}
onChange={() => toggleCheckedUser(company.companyID, contactID)}
/>
UPDATE, A POSSIBLE SOLUTION
I found that the following works:
To be able to access the current value from useState I used useRef
const selectedSendToRef = useRef();
useEffect( () => {
selectedSendToRef.current = selectedSendTo;
}, [selectedSendTo])
Then inside of my function, I can use selectedSendToRef.current to access the most recent value of `selectedSendTo.
When updating state, I can access the most recent version from state using
setSelectedSendTo( prevValue => ....)
const toggleCheckedUser = (companyID, contactID) => {
console.log(companyID, contactID, selectedSendToRef.current);
console.log('selectedSendTo[companyID]: ', selectedSendTo[companyID]);
let newValue;
if(selectedSendToRef.current[companyID] &&
selectedSendToRef.current[companyID][contactID] === true){
newValue = false;
}else{
newValue = true;
}
setSelectedSendTo(prevValue => (
{
...prevValue,
[companyID]:{
...prevValue[companyID],
[contactID]: newValue,
}
}
));
}
UPDATE 2: The Real Solution
Okay so it seems like the problem was that even after a render, the child component was not receiving the updated state because of how I had used nested functions to create the elements.
Here is how I had things
<Main Component>
<div>
{Object_1}
<div>
</Main Componenent
and object_1 was defined something like this:
const Object_1 =
<React.Fragment>
<h1>Random Header</h1>
{StateObject_Containg_Elements}
</React.Fragment>
Now to create the state object that conatined the elements I wanted to display I was using a funciton called by a useEffect hook. Basically when the server sent back data that I needed, I would tell the useEffect hook to run a function called createElements
const createElements = (data) => {
const elements = Object.keys(data).map( item => return(
<ul>
{subFunction1(item)}
</ul>
subFunction1(item){
item.contacts.map( name => {
reutrn <CustomCheckbox name={name} checked={selectedSendTo[name]}
})
}
saveElementsToState(elements);
}
As you can see we basically have a function that runs 1 time (on server response) that triggers a function that creates the array of elements that we want to display which has its own nested subfunction that includes the child component that we are asking to watch a different state object to know whether it should be checked or not.
So What I did was simplify things, I turned {Object_1} into it's own functional component, lets call it <Object1 />. Inside the component instead of calling a function I just put the function code in there to loop through and return the elements (no longer saving elements to state) and lastly I no longer needed the useEffect since just updating the state object with the data once it gets it from the server would cause my subcomponent to re-render and create the elements. Inside the sub-component I simply return null if the data in state is null.
That fixed all my problems.
so now it looks something like this:
const Object1 = () => {
if(!data)return null;
return(
Object.keys(data).map( item => return(
<ul>
{subFunction1(item)}
</ul>
subFunction1(item){
item.contacts.map( name => {
reutrn <CustomCheckbox name={name} checked={selectedSendTo[name]}
})
}
)
}
return(
<div>
<Object1 /> //This is what contains/creates the elements now
</div>
)
Basically i want to be able to detect if a react component has children which are overflowing. Just as in this question. I have found that the same thing is possible using ReactDOM, however i cannot/should not use ReactDOM. I don't see anything on the suggested alternative,ref, that is equivalent.
So what i need to know is if it is possible to detect overflow within a react component under these conditions. And to the same point, is it possible to detect width at all?
In addition to #jered's excellent answer, i'd like to mention the qualifier that a ref will only return an element that directly has access to the various properties of regular DOM elements if the ref is placed directly on a DOM element. That is to say, it does not behave in this way with Components.
So if you are like me and have the following:
var MyComponent = React.createClass({
render: function(){
return <SomeComponent id="my-component" ref={(el) => {this.element = el}}/>
}
})
and when you attempt to access DOM properties of this.element (probably in componentDidMount or componentDidUpdate) and you are not seeing said properties, the following may be an alternative that works for you
var MyComponent = React.createClass({
render: function(){
return <div ref={(el) => {this.element = el}}>
<SomeComponent id="my-component"/>
</div>
}
})
Now you can do something like the following:
componentDidUpdate() {
const element = this.element;
// Things involving accessing DOM properties on element
// In the case of what this question actually asks:
const hasOverflowingChildren = element.offsetHeight < element.scrollHeight ||
element.offsetWidth < element.scrollWidth;
},
The implementation of the solution proposed by #Jemar Jones:
export default class OverflowText extends Component {
constructor(props) {
super(props);
this.state = {
overflowActive: false
};
}
isEllipsisActive(e) {
return e.offsetHeight < e.scrollHeight || e.offsetWidth < e.scrollWidth;
}
componentDidMount() {
this.setState({ overflowActive: this.isEllipsisActive(this.span) });
}
render() {
return (
<div
style={{
width: "145px",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden"
}}
ref={ref => (this.span = ref)}
>
<div>{"Triggered: " + this.state.overflowActive}</div>
<span>This is a long text that activates ellipsis</span>
</div>
);
}
}
Yep, you can use ref.
Read more about how ref works in the official documentation: https://facebook.github.io/react/docs/refs-and-the-dom.html
Basically, ref is just a callback that is run when a component renders for the first time, immediately before componentDidMount is called. The parameter in the callback is the DOM element that is calling the ref function. So if you have something like this:
var MyComponent = React.createClass({
render: function(){
return <div id="my-component" ref={(el) => {this.domElement = el}}>Hello World</div>
}
})
When MyComponent mounts it will call the ref function that sets this.domElement to the DOM element #my-component.
With that, it's fairly easy to use something like getBoundingClientRect() to measure your DOM elements after they render and determine if the children overflow the parent:
https://jsbin.com/lexonoyamu/edit?js,console,output
Keep in mind there is no way to measure the size/overflow of DOM elements before they render because by definition they don't exist yet. You can't measure the width/height of something until you render it to the screen.
I needed to achieve this in React TypeScript, as such here is the updated solution in TypeScript using React Hooks. This solution will return true if there are at least 4 lines of text.
We declare the necessary state variables:
const [overflowActive, setOverflowActive] = useState<boolean>(false);
const [showMore, setShowMore] = useState<boolean>(false);
We declare the necessary ref using useRef:
const overflowingText = useRef<HTMLSpanElement | null>(null);
We create a function that checks for overflow:
const checkOverflow = (textContainer: HTMLSpanElement | null): boolean => {
if (textContainer)
return (
textContainer.offsetHeight < textContainer.scrollHeight || textContainer.offsetWidth < textContainer.scrollWidth
);
return false;
};
Lets build a useEffect that will be called when overflowActive changes and will check our current ref object to determine whether the object is overflowing:
useEffect(() => {
if (checkOverflow(overflowingText.current)) {
setOverflowActive(true);
return;
}
setOverflowActive(false);
}, [overflowActive]);
In our component's return statement, we need to bind the ref to an appropriate element. I am using Material UI coupled with styled-components so the element in this example will be StyledTypography:
<StyledTypography ref={overflowingText}>{message}</StyledTypography>
Styling the component in styled-components:
const StyledTypography = styled(Typography)({
display: '-webkit-box',
'-webkit-line-clamp': '4',
'-webkit-box-orient': 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
});
The same could be achieved using React hooks:
The first thing you need would be a state which holds boolean values for text open and overflow active:
const [textOpen, setTextOpen] = useState(false);
const [overflowActive, setOverflowActive] = useState(false);
Next, you need a ref on the element you want to check for overflowing:
const textRef = useRef();
<p ref={textRef}>
Some huuuuge text
</p>
The next thing is a function that checks if the element is overflowing:
function isOverflowActive(event) {
return event.offsetHeight < event.scrollHeight || event.offsetWidth < event.scrollWidth;
}
Then you need a useEffect hook that checks if the overflow exists with the above function:
useEffect(() => {
if (isOverflowActive(textRef.current)) {
setOverflowActive(true);
return;
}
setOverflowActive(false);
}, [isOverflowActive]);
And now with those two states and a function that checks the existence of an overflowing element, you can conditionally render some element (eg. Show more button):
{!textOpen && !overflowActive ? null : (
<button>{textOpen ? 'Show less' : 'Show more'}</button>
)}
To anyone who wonder how it can be done with hooks and useRef:
// This is custom effect that calls onResize when page load and on window resize
const useResizeEffect = (onResize, deps = []) => {
useEffect(() => {
onResize();
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps, onResize]);
};
const App = () => {
const [isScrollable, setIsScrollable] = useState(false);
const [container, setContainer] = useState(null);
// this has to be done by ref so when window event resize listener will trigger - we will get the current element
const containerRef = useRef(container);
containerRef.current = container;
const setScrollableOnResize = useCallback(() => {
if (!containerRef.current) return;
const { clientWidth, scrollWidth } = containerRef.current;
setIsScrollable(scrollWidth > clientWidth);
}, [containerRef]);
useResizeEffect(setScrollableOnResize, [containerRef]);
return (
<div
className={"container" + (isScrollable ? " scrollable" : "")}
ref={(element) => {
if (!element) return;
setContainer(element);
const { clientWidth, scrollWidth } = element;
setIsScrollable(scrollWidth > clientWidth);
}}
>
<div className="content">
<div>some conetnt</div>
</div>
</div>
);
};