ReactJS, find elements by classname in a React Component - javascript

I've a React component. Some elements will be inserted through the children. Some of these elements will have a specific classname.
How can I get a list of these DOM nodes in my outermost Component?
<MyComponent>
<div classname="snap"/>
<p></p>
<div classname="snap"/>
<p></p>
<div classname="snap"/>
</MyComponent>
What I want to know is how many elements with the classname "snap" are inserted in my component.

You can achieve it, via findDOMNode of react-dom, like below:
ReactDOM.findDOMNode(<instance-of-outermost-component>).getElementsByClassName('snap') // Returns the elements
If you need the count,
ReactDOM.findDOMNode(<instance-of-outermost-component>).getElementsByClassName('snap').length

You can use ReactDOM.findDOMNode. Even though the documentation encourage using ref, let's see how it works:
findDOMNode()
ReactDOM.findDOMNode(component)
If this component has been mounted into the DOM, this returns the
corresponding native browser DOM element. This method is useful for
reading values out of the DOM, such as form field values and
performing DOM measurements. In most cases, you can attach a ref to
the DOM node and avoid using findDOMNode at all.
When a component renders to null or false, findDOMNode returns null.
When a component renders to a string, findDOMNode returns a text DOM
node containing that value. As of React 16, a component may return a
fragment with multiple children, in which case findDOMNode will return
the DOM node corresponding to the first non-empty child.
Note: findDOMNode is an escape hatch used to access the underlying DOM
node. In most cases, use of this escape hatch is discouraged because
it pierces the component abstraction. findDOMNode only works on
mounted components (that is, components that have been placed in the
DOM). If you try to call this on a component that has not been mounted
yet (like calling findDOMNode() in render() on a component that has
yet to be created) an exception will be thrown. findDOMNode cannot be
used on functional components.
Also let's look at the ref, which is recommended:
Adding a Ref to a Class Component
When the ref attribute is used on a custom component declared as a
class, the ref callback receives the mounted instance of the component
as its argument. For example, if we wanted to wrap the CustomTextInput
above to simulate it being clicked immediately after mounting:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
this.textInput.focusTextInput();
}
render() {
return (
<CustomTextInput
ref={(input) => { this.textInput = input; }} />
);
}
}
Note that this only works if CustomTextInput is declared as a class:
class CustomTextInput extends React.Component {
// ...
}

Yoy can also use this.props.children to get number of child nodes with given class:
let snapCount = React.Children.toArray(this.props.children).filter((item) => item.props.className === 'snap').length;

I had a similar issue where document.getElementsByClassName was not returning what I needed. I found that using document.querySelectorAll did the trick. In terms of the code in question:
const elements = document.querySelectorAll(["classname=snap"])
const length = elements.length
Why does this work? As per https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector, document.querySelector will return the first element within the document that matches the specified selector. Therefore, document.querySelectorAll will return all elements within the document that matches the specified selector.
I know this post is quite old but hopefully the above can help someone in a similar position to me. I came to the conclusion that document.getElementsByClassName probably doesn't work with React because according to https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName this is applicable to elements that have a class as opposed to a className.

Related

Access list of siblings in React

Parent element (Board) creates list of children and passes them method to access this list, like this:
export default class Board extends React.Component {
constructor(props) {
super(props);
this.getList = this.getList.bind(this);
const nodes = this.props.data.map(item => (
<BoardItem key={item.id} next={item.next} accessSiblings={this.getList} />
));
this.state = {data: this.props.data, nodes: nodes}
}
getList() {
return this.state.nodes;
}
render() {
return (
<div>
{this.state.nodes}
</div>
);
} }
Then I call update() method and receive this list, filter it and correctly get the required object:
update() {
console.log(this.props.accessSiblings().find(x => x.key == this.props.next));
}
However it returns Symbol(react.element), and I am trying to get such properties as "offsetTop", "offsetHeight" of already rendered element.
Basically, I want to call an event in one element that gets some DOM properties of sibling element(e.g. offsetTop) and changes the state of this sibling.
Is this the correct approach? It feels very hacky and doesn't work at the moment.
You need to use "refs" (see also: how to access a dom element in react).
However, you should avoid working with DOM objects, if possible.
You do need a ref for accessing offsetTop etc., but apart from that you should not pass DOM or ReactElements, but you should only work with state (like "plain javascript" objects) as far as possible, and then render ReactElements (JSX, like <BoardItem ...) as the last step, and never care about DOM elements (React does it for you).
It is also usually not necessary to store ReactElements in variables or state, I suggest to try if you can focus a little bit more on state, and understand JSX more as a way to view the state.

get ref once all descendants are finished rendering

I'm working on a react-leaflet custom control, and I have a component whose ref I need. But this component has dynamically rendered children. Something like this:
<LayersControl ref={controlRef => this.controlRef = controlRef}>
{this.props.children}
</LayersControl>
The children have their own children as well. As these children are rendered, they affect the underlying leaflet logic.
(For those not familiar, or curious, LayersControl will create an L.control.layers. The children of the LayersControl will always be a LayersControl.BaseLayer or LayersConrol.Overlay. Those will then have children, which could be any number of not-yet-known react-leaflet layers or components. As the .BaseLayer or .Overlay components mount, they modify the underlying L.control.layers object.)
I am trying to get a reference to the leaflet instance of the component after all descendants have rendered. However, calling the ref function as I've called above, that's not what happens. While my ref is indeed defined in the componentDidMount, it is incomplete. For example, some of the crucial properties that I need from within cdm are not yet available (i.e. the ._container property).
I know that his is how refs are intended to work - they give you a ref as soon as the root element has rendered and mounted, even if the children and descendants have not yet mounted. Short of doing some hack like doing a setTimeout and getting the ref after 10ms, how can I get a ref of this element once all descendants are done rendering?
Working demo of the issue

Can somebody explain what makes this code?

I've hot React component which returns input tag. Can you please explain what is going on at the eighth line ref={element => element && (element.onChange = onChange)}? I
import React from 'react';
export default function MyInput({
onChange,
...rest
}) {
return (
<input
{...rest}
ref={element => element && (element.onChange = onChange)}
/>
);
}
React's ref is used to access the DOM directly, and in general is recommended to use as less as possible. The point of functional refs, and keep in mind that they're deprecated, is to assign the element into a class component's variable. e.g.:
Class MyComponent extends Component {
constructor(props) {
super(props);
this.inputRef = null;
}
...stuff
render() {
...stuff
<input ref={element => this.inputRef = element} />
}
}
Then, you could do something like:
this.inputRef.current.style.color = 'blue';
In your case, there is no need for this. If you want to assign the onChange you get from props, just do this:
<input {...stuff} onChange={onChange} />
Read more about React refs here.
As for element && element.onChange, it's designed to make sure that element exists before accessing it's onChange property. Another way to do it, using optional chaining (only avaliable in react-scripts v3.3 and above), is this:
element?.onChange
Refs are used to access DOM elements
The value of ref differs depending on the type of node:
When the ref attribute is used on an HTML element, the ref created
in the constructor with React.createRef() receives the underlying
DOM element as its current property.
When the ref attribute is used on a custom class component, the ref
object receives the mounted instance of the component as its
current.
They are used in cases where we want to change the value of a child component, without making use of props and all. But in your case, i think you have no need to use ref because you simply wants to assign onChange that you received from props.

React access DOM element instead of object thru refs

I'm trying to access DOM element in react because it need for third party library.
And I'm able to do it with refs for built in react elems.
Like <div ref={this.someRef} <span ref={this.otherRef} etc.
And I can access DOM elem thru this.someRef.current
But when I'm trying to do same trick for custom elements <SomeCustomElem ref={this.anotherRef}, this.anotherRef.current returns me an object of values and I dont see any way to access DOM elem with custom components.
Is there any chance to get access to DOM of custom elem?
You can use react-dom to access any type of DOM element which is shipped with react.
To access the DOM pass a ref with the react element and latter access it with findDOMNode method.
Example:
import ReactDOM from 'react-dom';
...
let reactElement = ReactDOM.findDOMNode(this.refs.refName)
...
<Component ref='refName'/>
This depends on what kind of component SomeCustomElem is.
For <SomeCustomElem ref={this.anotherRef}/>, ReactDOM findDOMNode can be used:
findDOMNode(this.anotherRef.current);
This cannot be done if SomeCustomElem is functional component. Neither ref nor findDOMNode will work on it.
ReactDOM.findDOMNode(<Any React Element>) //Returns a DOM node
//eg. Using "this" inside a react el.
ReactDOM.findDOMNode(this).scrollIntoView()
Make sure to try-catch it as it might return null
findDOMNode is suggested to avoid:
findDOMNode is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.
You can forward ref instead.
But just think about that in a way "ref links you to given component". So maybe you just use separated prop like that:
class CustomComponent extends Component {
render() {
return (<div ref={this.props.outerDivRef}> .... </div>);
}
}
class Parent extends Component {
constructor() {
this.innerRef = React.createRef();
}
render() {
return (<CustomComponent outerDivRef={this.innerRef} />);
}
}
Besides last pattern is older than ref forwarding feature but it looks like more flexible one.

How to check if Dom Element or React Component

When creating an HOC I'm not sure which kind of component will be wrapped, sometimes it is another React Component, sometimes it could be a plain DOM Element as li and a.
WrappedComp = myHOC(BaseComponent)
MyHOC will pass extra props to the wrapped component and in most of the cases this will work as it should.
But sometimes when BaseComponent is for example an li it will not accept the extra props and React will throw a warning Unkown Prop Warning saying that DOM element do not accept non-standard dom attributes: https://facebook.github.io/react/warnings/unknown-prop.html
So how could I check if BaseComponent is a DOM element or else?
In case it is I will not pass the extra props to it.
Is there a better way to do this?
Short answer:
Check if element is of type string to check if element is a DOM element.
Check if element is of type function to check if element is a React component.
Example:
if (typeof BaseComponent.type === 'string') {
return BaseComponent
}
// add props
Long answer:
As defined in the React documentation, built-in components like <li> or <span> results in a string 'li' or 'span' being passed to React.createElement, e.g. React.createElement("li").
Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.
Consequently, a React Component is of type function, while a DOM Component is of type string.
The following WrapperComponent logs the typeof child.type of each child element. The output is function, string, string.
function WrappedComponent({children}) {
return React.Children.map(children, child => {
console.log(typeof child.type)
...
})
}
const BaseComponent = ({children}) => children
function App() {
return (
<WrappedComponent>
<BaseComponent>This element has type of function🔥</BaseComponent>
<span>This element has type of string</span>
<li>This element has type of string</li>
</WrappedComponent>
)
}
Check if BaseComponent is a React Component, and add the required props.
if(BaseComponent.prototype.isReactComponent){
//add props
}

Categories