How to check if Dom Element or React Component - javascript

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
}

Related

React clone element to modify a child component and keep ref in a functional component

I used to have refs in my component when rendering, and it worked:
// props.children is ReactElement<HTMLDivElement>[]
const [childRefs] = useState<RefObject<any>[]>(props.children.map(() => createRef()));
// working code, all the variables (props, childRefs etc) are defined earlier in scope
return <div {...props}>
{
props.children.map((c, i) => <div key={i} ref={childRefs[i]}>{c}</div >)
}
</div>
Basically I'm using the refs to imperatively directly set some transforms to style as mouse moves on JS mousemove event.
However, I now need to inject some CSS class into the passed component automatically. I've created a component (named Layer) that takes the child element, clones it, sets the CSS class, and returns it:
function Layer(props:LayerProps){
const elem = cloneElement(props.children, {...props.children.props,
className: styles['layer']
});
return elem;
}
I've updated the main component like this too:
return <div {...props}>
{
props.children.map((c, i) => <Layer key={i} ref={childRefs[i]}>{c}</Layer>)
}
</div>
However now my refs aren't passed now, understandibly, as the Layer functional component can't have a ref (as it's a function). When I try to set the ref to Layer it can't, and have this error (understandably):
(property) ref: React.RefObject<any>
Type '{ children: ReactElement<HTMLDivElement, string | JSXElementConstructor<any>>; key: number; ref: RefObject<any>; }' is not assignable to type 'IntrinsicAttributes & LayerProps'.
Property 'ref' does not exist on type 'IntrinsicAttributes & LayerProps'.ts(2322)
If I try to forward the ref using forwardRef it doesn't have anything to set that ref to as I'm just modifying the passed child element and returning it, not returning a new element like <div>...</div> that I could forward ref to like <div ref={forwardedRef}>...</div>.
How can I modify the CSS class and keep a ref to the object? I know how to do each one (if I just need to add class I cloneElement, if I just need to ref it I use forwardRef and pass it to the child component in JSX) yet I couldn't figure out being able to do both at the same time.
How can I do it?
Okay, after a bit digging and experimenting I've realized I can give ref "prop" (which isn't technically a real prop, but anyway) in cloneElement just like any prop.
I've ended up forwarding ref to the functional component, and provided ref as a prop to the newly cloned element, and it worked.
Yet, the TypeScript definitions are incorrectly flagging ref property as non-existent while it works perfectly. I needed it to cast the props to any to silence the linter error though:
const Layer = forwardRef((props:LayerProps, ref:any) => {
const elem = cloneElement(props.children, {...props.children.props,
className: `${props.children.props.className ?? ''} ${styles['layer']}`,
ref: ref
} as any); // as any fixes ref complaining
return elem;
});
And in many component:
return <div {...props}>
{
props.children.map((c, i) => <Layer key={i} ref={childRefs[i]}>{c}</Layer>)
}
</div>

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.

Warning: Unknown event handler property `onHeaderClick`. It will be ignored

I am creating Higher order components for passing some props with another component. But getting the warning for Unknown event handler property.
export class TableHeaderRow extends React.PureComponent{
render() {
const customCell = WrappedCellComponent(Cell,this.props.onHeaderClick, this.props.isMasterChecked, this.props.onTableCheckBoxselection);
return (
<TableHeaderRowBase
cellComponent={customCell}
rowComponent={Row}
contentComponent={Content}
titleComponent={Title}
showSortingControls={true}
sortLabelComponent={this.props.sortLabelComponent}
groupButtonComponent={(data: any) : any => null}
showGroupingControls={false}
{...this.props}
/>
)
}
}
const WrappedCellComponent = (WrappedComponent, onHeaderClick,checkIfMasterChecked, onTableCheckBoxSelection) => {
class HOC extends React.Component {
render() {
return <WrappedComponent
{...this.props}
onHeaderClick={onHeaderClick}
onTableCheckBoxSelection={onTableCheckBoxSelection}
checkIfMasterChecked={checkIfMasterChecked}
/>;
}
}
return HOC;
};
Events are working, but I am getting error in chrome devTool (i.e. Warning: Unknown event handler property onTableCheckBoxSelection. It will be ignored.)
This basically happens when you pass a prop with a name starting with on, regardless of its case. React assumes and tries to bind it with javascript events like onClick, onKeypress, etc
The error is well documented:
The unknown-prop warning will fire if you attempt to render a DOM
element with a prop that is not recognized by React as a legal DOM
attribute/property. You should ensure that your DOM elements do not
have spurious props floating around.
Found the correct answer in another post.
https://stackoverflow.com/a/50196327/1734744
The problem was with passing props from parent to child using {...props} which may unintentionally pass the parent's event handlers to the child.
Also, I was passing the {...props} to a div (the container of the child component) and my custom event handlers were not recognized by div (native html tag)
Hope this helps.

ReactJS, find elements by classname in a React Component

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.

How to deal with component reference variables and props in React 0.12+?

Consider this naive routing mechanism:
componentDidUpdate(prevProps, prevState) {
if (window.location.pathname === '/login') {
this.setState({handler: <LoginScreen someProp="someValue"/>});
}
}
render() {
return <div>
{this.state.handler}
</div>;
}
So far so good. But how does one override props — or more importantly, set a ref — on the child component?
cloneWithProps() no longer supports ref, and you're not allowed to use createFactory() in a JSX file. You're not allowed to use createElement in a JSX either:
componentDidUpdate(prevProps, prevState) {
if (window.location.pathname === '/login') {
this.setState({handler: LoginScreen});
}
}
render() {
var handler = React.createElement(this.state.handler, {ref: "foo"});
return <div>{handler}</div>;
}
Is there some undocumented behaviour allowing you to do something with either a component class reference, or an instance of such, in a JSX file?
You can absolutely use createElement and createFactory in a JSX file. The only thing to note is that the <X/> is essentially a createElement operator, so just like you can't do <<X/>/> you can't create an element and pass it to the operator.
What you're doing is completely fine. As long as you store the component class or factory, and not the element (instance) you won't have any problems.
In 0.12+ JSX differentiates between lowercase and uppercase component names, so if you want to use a dynamic component in JSX just alias it to a PascalCase variable.
render() {
var Handler = this.state.handler;
return <div><Handler ref="foo" /></div>;
}
Due to other changes Handler could either be a component class or a tag name string, e.g. 'div'.

Categories