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.
Related
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>
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.
I'm trying to insert the innerHTML for the div container ed. But I cannot get it after the React has render it. I understand that it's the problem with the stages of render, because I get null for this div container. What is I'm making the wrong?
class Test extends React.Component {
render() {
return (
<div>
<div id='ed'>
<p>{this.props.prop.text}</p>
</div>
{document.querySelector('#ed').innerHTML = this.props.prop[1]} // this the problem
</div>
);
}
}
ReactDOM.render(
<Test prop={store.getState()} />,
document.getElementById('root')
);
Your direct DOM manipulation won't work cause you called it in render().
You called Query selector in render(), Query selector or 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 Query selector or findDOMNode() in render() on a component that has yet to be created) an exception will be thrown.
you can do expressions in render() usually, but you can't get access the DOM element in render() since it is placing your elements in render() to DOM.
Use lifeCycle methods instead and You can use ReactDOM.findDOMNode(this) to access the underlying DOM node. But accessing the DOM node and manipulating like you do is against the React style of programming.
Query selector shouldn't be necessary with react just attach a ref to the element you want and you have access to it within any function of the react component.
Example Demo : demo
Try using the lifecycle event componentDidMount
class Test extends React.Component {
componentDidMount() {
const element = document.querySelector('#ed');
if (element) {
element.innerHTML = this.props.prop[1]
}
}
render() {
return (
<div>
<div id='ed'>
<p>{this.props.prop.text}</p>
</div>
</div>
);
}
}
You need to wait for the component to mount. You can do this by putting your code in a componentDidMount method.
componentDidMount() {
document.querySelector('#ed').innerHTML = "woo"
}
You may also reference the container div with ref={node => this.node = node}
I have a react-redux app which requires a part which is conditional.
i.e. it can either be a div with a certain className or a div with the same className and an onClick handler which fires an action. so I created a function which returns jsx according to the above conditions.
Now the problem is that the onClick is not being added to the props as I expected but className is working fine.
class someClass {
renderSomething(){
return <div className="someClass" onClick={() =>
this.props.someAction()}>Something</div>
}
render(){
{this.renderSomething()}
}
}
What I expected it to be
<div className="someclass" onClick={() => this.props.someAction()}>Something</div>
What Rect dev tools show
<div className="someclass">Something</div>
Don't know where I went wrong.
Edit 1: The function was mistakenly written outside the class.
You have a typo. In your renderSomething() method, you should have the following:
renderSomething() {
return (
<div className="someclass" onClick={this.props.someAction}>Something</div>
);
}
EDIT: Now I saw that renderSomething was not a part of class from it was called from. In order to keep reference to this, you have to move it inside of your class, hence make it class method.
If you wish to add an argument to someAction function, I suggest you add a event handler to your React component, and then assign it to a onClick. Using arrow function in render method causes creation of a new function in memory every time component re-renders.
Maybe you meant to do this?
class someClass {
renderSomething(){
return <div className="someClass" onClick={() => this.props.someAction()}>Something</div>
}
render(){
{this.renderSomething()}
}
}
In your original example, renderSomething wasn't a part of someClass -- so calling this.renderSomething() would have thrown a runtime error.
I could be wrong, but i think
render(){
return {this.renderSomething()}
}
Is the way to go. Let me know if render(){this.renderSomething()} works.
I need to remove a prop from a child.
I have a container element which uses a property on it's children to perform some enhancements on the children. That property should be removed from the child before rendering.
<AsyncContainer>
<Button onClick={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
The asyncHandler property should be removed from the button before rendering.
AsyncContainer uses React.cloneElement(child, properties).
I've tried nulling the asyncHandler property, setting it to undefined and deleting the property from the child.props. It seems that it is impossible to get rid of this property again.
I just ran into this issue. You can just create a new element and use the old element's type and props you want to pass through. I'm not sure if this an anti-pattern or not, I just stumbled on it and it seems to be working well so far.
It should look something like this:
function AsyncContainer(props) {
const child = React.Children.only(props.children)
const { asyncHandler, ...childProps } = child.props
// do asyncHandler stuff
return React.createElement(child.type, childProps)
}
function AsyncContainer(props) {
const child = React.Children.only(props.children);
return React.cloneElement(
child,
{ asyncHandler: undefined }
);
}
How it works
You clone element using React.cloneElement because element is immutable and only way to change its props is to create clone.
Use second React.cloneElement argument to add new props and remove old props. Unneeded props should be assigned with undefined. You need to do this because by default cloned element is cloned with all its props.
As per the comments you cannot modify the props directly as they are immutable.
However, I think I have a simple solution to this problem. I have no idea what library that is or how it works, so this may or may not work. However, this is a general answer to how you would remove a prop before a component gets mounted.
That being said, I would try to create my own component which renders a <Button />:
class MyButtonComponent extends React.Component {
...
render() {
return <Button onClick={this.props.onClickHandler} />;
}
}
Then in the component you want to do your enhancements:
render() {
<AsyncContainer>
<MyButtonComponent onClickHandler={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
}
This way you maintain your onClick eventlistener on the <Button /> component but you don't pass the illegal asyncHandler prop.
Edit:
Alternatively, you could also do:
class MyButtonComponent extends React.Component {
...
componentWillMount() {
let newProps = this.props;
delete newProps.asyncHandler;
this.setState({properties: newProps}):
}
render() {
return <Button {...this.state.properties} />;
}
}
This will apply all the props (with the spread operator) to <Button /> except for asyncHandler which we delete prior to the component being mounted by creating a copy of the props in state but with asyncHandler removed.
Also check this answer I gave to a similar question.