clientWidth function in React - javascript

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>
);
};

Related

How do I reference an element that is to be returned by a React component?

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.

Alternative for document.querySelector in react js

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').

How to provide window width to all React components that need it inside my app?

So I've got this hook to return the windowWidth for my App components. I'll call this Option #1.
import {useEffect, useState} from 'react';
function useWindowWidth() {
const [windowWidth,setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowWidth;
}
export default useWindowWidth;
And right now I'm basically using it on every component that depends on the window width to render, like:
function Component(props) {
const windowWidth = useWindowWidth();
return(
// RETURN SOMETHING BASED ON WINDOW WIDTH
);
}
And since the hook has an event listener for the resize events, the component stays responsive even after window resizes.
But I'm worried that I'm attaching a new listener for every component that uses that hook and it might slow things down at some point. And I've though of other approach:
Option #2
I use the useWindowWidth() hook only one time, inside a top level component like <App/> and I'll provide the windowWidth value down the chain via context.
Like:
function App() {
const windowWidth = useWindowWidth();
return(
<WindowWidthContext.Provider value={windowWidth}>
<Rest_of_the_app/>
</WindowWidthContext.Provider>
);
}
And then, every component that needs it could get it via:
function Component() {
const windowWidth = useContext(WindowWidthContext);
return(
// SOMETHING BASED ON WINDOW WIDTH
);
}
QUESTION
Am I right in being bothered by that fact that I'm setting up multiple resize listeners with Option #1 ? Is Option #2 a good way to optmize that flow?
If your window with is used by so many components as you mentioned, you must prefer using context. As it reads below:
Context is for global scope of application.
So, #2 is perfect choice here per react.
First approach #1 might be good for components in same hierarchy but only up-to 2-3 levels.
I'm not sure if adding and removing event listeners is a more expensive operation than setting and deleting map keys but maybe the following would optimize it:
const changeTracker = (debounceTime => {
const listeners = new Map();
const add = fn => {
listeners.set(fn, fn);
return () => listeners.delete(fn);
};
let debounceTimeout;
window.addEventListener('resize', () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(
() => {
const width=window.innerWidth;
listeners.forEach(l => l(width))
},
debounceTime
);
});
return add;
})(200);
function useWindowWidth() {
const [windowWidth, setWindowWidth] = useState(
() => window.innerWidth
);
useEffect(
() =>//changeTracker returns a remove function
changeTracker((width) =>
setWindowWidth(width)
),
[]
);
return windowWidth;
}
As HMR said in an above thread, my solution was to use redux to hold the width value. With this strategy you only need one listener and you can restrict how often you update with whatever tool you like. You could check if the width value is within the range of a new breakpoint and only update redux when that is true. This only works if your components dont need a steady stream of the window width, in that case just debounce.

Has anyone successfully used react-testing-library to test change events on a draftJS Editor component?

​The fireEvent.change() just doesn't work.
It says there are no setters on the element.
I tried using aria selectors instead
const DraftEditor = getByRole('textbox')
DraftEditor.value ='something';
fireEvent.change(DraftEditor);
I tried same again using query selector
const DraftEditor = container.querySelector('.public-DraftEditor-content'));
Tried keyboard events instead.
Nothing.
Has anyone managed to text a rich text input with draftjs and react testing library?
I managed to do it by getting inspiration from some issue description for draft-js and adapting that to our case at hand
import { createEvent } from "#testing-library/dom"
import { render, fireEvent } from "#testing-library/react"
function createPasteEvent(html) {
const text = html.replace('<[^>]*>', '');
return {
clipboardData: {
types: ['text/plain', 'text/html'],
getData: (type) => (type === 'text/plain' ? text : html),
},
};
}
renderedComponent = render(<App />)
const editorNode = renderedComponent.querySelector(".public-DraftEditor-content")
const eventProperties = createPasteEvent(textToPaste)
const pasteEvent = createEvent.paste(editorNode, eventProperties)
pasteEvent.clipboardData = eventProperties.clipboardData
fireEvent(editorNode, pasteEvent)
Some additional notes:
renderedComponent in my case is the parent element in which the Editor component is rendered.
apparently, 'ClipboardEvent' is not implemented in JSDOM (see list of supported events), therefore, the call to createEvent.paste creates a generic Event, and not a ClipboardEvent. As a workaround, I copy the necessary clipboardData properties again to the generated generic event so that they will be taken into account by the function editOnPaste of the Draft-js editor, which itself will be triggered because of the fired event.
I managed to get it working mocking the editor and intercepting the onChange method so you can still execute all the lib functions:
const draftjs = require('draft-js');
draftjs.Editor = jest.fn(props => {
const modifiedOnchange = e => {
const text = e.target.value;
const content = ContentState.createFromText(text);
props.onChange(EditorState.createWithContent(content));
};
return <input className="editor" onChange={e => modifiedOnchange(e)} />;
});

How to detect overflow of React component without ReactDOM?

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>
);
};

Categories