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').
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 am using Wordpress as my CMS and creating some of the pages in React and rendering it. I have a use case where i need to render HTML DOM Node inside of my React Component. I am getting the DOM Node with the help of querySelector() which returns me the DOM node. In React i am trying to render it using React.createElement. But it renders as [object HTMLDivElement] on my page.
render() {
const { reactPages } = this.props;
const innerPages = React.createElement('div', {id: "myDiv"}, `${reactPages.children[0]}`);
return (
<div className="react-cmp-container">
<h3>React Container</h3>
{ innerPages }
</div>
)
}
}
The other thing i tried was using the dangerouslySetInnerHTML:
rawMarkUp = () => {
const { reactPages } = this.props;
return {__html: reactPages}
}
render() {
const innerPages = React.createElement('div', {id: "myDiv"}, )
return (
<div className="react-cmp-particle-container">
<h3>React Particle Container</h3>
<div dangerouslySetInnerHTML={this.rawMarkUp()}></div>
</div>
)
}
This is the type of DOM Node that i am getting and passing it as props to my Component.
This is not recommended to to. It might cause many kinds of trouble later. Anyway, ...
querySelector() returns a DOM-element (or collection of such).
1. React.createElement
React.createElement does not work, because it expects another React.Element, or a string, not a DOM-element.
2. dangerouslySetInnerHTML
dangerouslySetInnerHTML does not work, because it expects a HTML string, not a DOM-element.
You could use domElement.innerHTML and put the HTML string into dangerouslySetInnerHTML, but that might not represent the DOM element as you want it.
3. useRef
You could add a ref to some element that is rendered by React. This gives you access to the corresponding DOM-element (rendered by React), and you can inject your DOM-element (returned by querySelector()) using normal DOM methods.
This disables React to "track what's going on", and you might experience inconsistent state and hard-to-understand bugs.
e.g.:
export const StaticHtml = (props)=>{
const ref = useRef();
useEffect(() =>{
var elm = document.getElementById('some-static-html');
ref.current.appendChild(elm);
}, []);
return (<div ref={ ref }>
some div
</div>);
};
I want to insert some props to a React component which I have extracted out of props.children, like so:
<PageContainer>
<PageOne />
<PageTwo />
<PageThree />
</PageContainer>
Inside <PageContainer> i am extracting the current page via props.children and current page index, something like this:
const { children, pageIndex } = props;
let activePage = React.Children.toArray(children)[pageIndex];
Inside this PageContainer I have the "oportunity" to send down a function that I need inside the <PageOne>, <PageTwo> and <PageThree>. I tried something like this, but then I got some compiling problems, I think. Since this worked locally and not in my test environment:
const newProps = React.cloneElement(activePage.props.children, { myWantedFunction: myWantedFunctionThatIsAvailableInsidePageContainer });
activePage = React.cloneElement(activePage, { children: newProps });
The problem here is that myWantedFunction is not working in the test environment, but it is working locally. It says that it is not a function in the test environment, but when I console.log it out locally, it prints out a function. I am guessing there is a compiling problem, but I am wondering if there is a better way to acheive this (send down props to a component stored in a variable that I got out of props.children)?
Thank you in advance!
You are almost correct, you need to use React.cloneElement() to send props down to the child component. First you need to get the active page which you're doing right:
let activePage = React.Children.toArray(children)[pageIndex];
Then you need to use React.cloneElement to pass props to this component like this. Let's the name of the prop is isAuthenticated which is a boolean:
let component = React.cloneElement(activePage, { isAuthenticated: true })
Now in your page you'll be able to access this prop: prop.isAuthenticated
I created a working demo for you, have a look:
https://codesandbox.io/s/determined-kowalevski-eh0ic?file=/src/App.js
Hope this helps :)
Alternatively you could also do something like this:
const PAGES = [PageOne, PageTwo, PageThree]
function PageContainer(props){
const {pageIndex} = props
const ActivePage = PAGES[pageIndex]
return <ActivePage someFunction={someFunction} />
function someFunction(){
// the function you want to pass to the active page
}
}
Node that ActivePage has a capital A, which allows it to be used as a JSX component.
A drawback of this solution is however that if you use typescript it wont type check the props correctly since you only know which component is to be rendered at runtime. You could replace the array lookup with a switch to avoid that issue.
Yet another variation would be to just let the Page components handle their own state and pass the function to all of them. Like this:
function PageContainer(props){
const {pageIndex} = props
const someFn = () => 0
return <React.Fragment>
<PageOne id={1} activePage={pageIndex} someFunction={someFn} />
<PageTwo id={2} activePage={pageIndex} someFunction={someFn} />
<PageThree id={3} activePage={pageIndex} someFunction={someFn} />
</React.Fragment>
}
then in the Page Components just check if the pageIndex corresponds to their id:
function PageOne(props){
const {id, activePage, someFunction} = props
if (id === activePage){
const result = someFunction()
return <div>Page One: {result}</div>
}
}
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>
);
}
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)} />;
});