Pass JSX to child, and have the child edit JSX props? - reactjs

How can <MyList> add prop myListProp={true} to <SomeComponent1>?
I want MyList to manage props, and not the class where this JSX is declared (theCurrentClass)
<div className='theCurrentClass'>
<MyList>
<SomeComponent1 />
<OtherComponent2 />
<WowComponent3 />
</MyList>
</div>
Is cloneElement the only way?
I need to constantly update these props / styles, and don't want to continuously clone the JSX.

This does seem to be accounted for, both with cloneElement and JSX.
https://facebook.github.io/react/docs/react-api.html#cloneelement
return this.props.children.map((child, index) => {
return React.cloneElement(child, {
...child.props,
});
});
return this.props.children.map((child, index) => {
return ( <child.type {...child.props}></child.type> );
});
I can interface with the element by adding props the same way I do with any rendered element
NOTE: requestAnimationFrame loops should only edit the ref (passed back from the child in a callback on didMount), and rarely update props unless you want to deal with all the React "smart" analysis bogging down the 60fps

Related

How to get react grand-child node to render when child is out of my control

I have a react component which uses a 3rd party library Component as a Child node. The Grand children (or the children of the 3rd party libary) are under my control. When my component receives new props it re-renders, however the 3rd party component seems to stop my components grand-children from re-rendering also, even though the props my component received, are passed to the non-re-rendering components directly
If I remove the 3rd party component then my component re-renders as do the grand-children.
render() {
<div>
<ThirdPartyComponent props={blah}>
{this.props.products.map(prod => <MyGrandChildrenComponents product={prod} />
</ThirdPartyComponent>
</div>
}
A concrete example can be found on this code sandbox: codesandbox.io/s/silly-grass-7frlx
I'd expect my MyGrandChildrenComponents component to get updated when this.props.products changes... Any hints?
This can happen if ThirdPartyComponent is a stateful component and is not handling its prop updates correctly. One way to force a re-render is to add a key prop to your ThirdPartyComponent and update its value when a re-render is needed.
render() {
<div>
<ThirdPartyComponent key={something-that-changes-when-rerender-needed} props={blah}>
{this.props.products.map(prod => <MyGrandChildrenComponents product={prod} />
</ThirdPartyComponent>
</div>
}
If ThirdPartyComponent is a PureComponent it should re render (and so re render its children) when one of its props changes so you can try:
render() {
<div>
<ThirdPartyComponent props={blah} products={this.props.products}>
{this.props.products.map(prod => <MyGrandChildrenComponents product={prod} />
</ThirdPartyComponent>
</div>
}
To trigger re renders when this.props.products changes.
But any prop should do.
If ThirdPartyComponent has a custom implementation of shouldComponentUpdate, then you will have to find the specific prop which triggers update if it exists.

How to get the ref of a child component inside parent component in ReactJS

I have a parent component and a child component as following.
Parent component:
constructor(){
this.refs = React.createRef();
}
setRef(ref) {
console.log(ref)
}
handleChange(e) {
console.log(e)
}
render() {
return(
<ChildComponent ref={this.setRef} handleChange={this.handleChange.bind(this)}/>
)
}
Child Component:
render() {
return(
<input type="text" ref={this.props.ref} onChange={this.props.handleChange.bind(this)} > </input>
)
}
What changes should I do to get the ref value inside the handleChange() function in parent component? Thanks in advance.
If I´m reading correctly you want to access the input element from the parent component?
You have to use another name for your prop as ref is a keyword prop which will automatically assign the component the given variable.
<ChildComponent inputRef={this.setRef} handleChange={this.handleChange.bind(this)}/>
class ChildComponent extends Component {
render() {
return(
<input type="text" ref={this.props.inputRef} onChange= {this.props.handleChange.bind(this)} > </input>
);
}
}
Based on how you want to access the ref you can either set this.refs directly to the prop or set it inside your setRef function.
// either `this.refs` and then usable through `this.refs.current`
<ChildComponent inputRef={this.refs} {...} />
// or `this.setRef` and assign it yourself
setRef = (ref) => {this.refs = ref;}
ref(as well as key btw) is very special prop. It is not accessible in child by this.props.ref.
Shortest way is to use different prop to pass ref forward and backward:
class Parent ...
render() {
...
<Child inputRef={this.inputRef} />
class Child
...
render() {
<input ref={this.props.inputRef} ...
It's most flexible since you may access different ref inside you child component(e.g. inputRef + scrollableContainerRef + popupRef)
But in some cases you want to compose new component for existing code base. Say <input>'s replacement. Sure, in this case you would avoid changing all <input ref={...} /> onto <MyInput refProp={...}>.
Here you may use React.forwardRef. It feels the best with functional components like
export Child = React.forwardRef((props, forwardedRef) => {
...
return ...
<input ref={forwardedRef} />
})
But for class-based component you would rather use ref-prop with different name:
class Child
...
render()
return
...
<input ref={this.props.forwardedRefName} />
export ChildWithForwardRef = React.forwardRef((props, ref) => <Child forwardedRefName={ref} {...props} />)
PS since you will consume what forwardRef returns rather initial component(Child) you may want to specify displayName for it. This way you may be able find it later say with enzyme's find() or easy recognize element in browser's React DevTools
ref work only on native dom element, to make ref work on user defined Component, you need this:
https://reactjs.org/docs/forwarding-refs.html

REACT Warning Unknown props parsing to child component [duplicate]

I've built my own custom react-bootstrap Popover component:
export default class MyPopover extends Component {
// ...
render() {
return (
<Popover {...this.props} >
// ....
</Popover>
);
}
}
The component is rendered like so:
// ... my different class ...
render() {
const popoverExample = (
<MyPopover id="my-first-popover" title="My Title">
my text
</MyPopover >
);
return (
<OverlayTrigger trigger="click" placement="top" overlay={popoverExample}>
<Button>Click Me</Button>
</OverlayTrigger>
);
}
Now, I want to add custom props to MyPopover component like that:
my text
And to use the new props to set some things in the popover
for example -
<Popover {...this.props} className={this.getClassName()}>
{this.showTheRightText(this.props)}
</Popover>
but then I get this warning in the browser:
Warning: Unknown props popoverType on tag. Remove these props from the element.
Now, I guess that I can just remove the {...this.props} part and insert all the original props one by one without the custom props, but In this way I lose the "fade" effect and also it's an ugly way to handle this problem. Is there an easier way to do it?
Updated answer (React v16 and older):
As of React v16, you can pass custom DOM attributes to a React Component. The problem/warning generated is no longer relevant. More info.
Original answer (React v15):
The easiest solution here is to simply remove the extra prop before sending it to the Popover component, and there's a convenient solution for doing that.
export default class MyPopover extends Component {
// ...
render() {
let newProps = Object.assign({}, this.props); //shallow copy the props
delete newProps.popoverType; //remove the "illegal" prop from our copy.
return (
<Popover {...newProps} >
// ....
</Popover>
);
}
}
Obviously you can (and probably should) create that variable outside your render() function as well.
Basically you can send any props you want to your own component, but you'd have to "clean" it before passing it through. All react-bootstrap components are cleansed from "illegal" props before being passed as attributes to the DOM, however it doesn't handle any custom props that you may have provided, hence why you have to do your own bit of housekeeping.
React started throwing this warning as of version 15.2.0. Here's what the documentation says about this:
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.
[...]
To fix this, composite components should "consume" any prop that is intended for the composite component and not intended for the child component.
For further reading, check this page from the official react site.

Add custom props to a custom component

I've built my own custom react-bootstrap Popover component:
export default class MyPopover extends Component {
// ...
render() {
return (
<Popover {...this.props} >
// ....
</Popover>
);
}
}
The component is rendered like so:
// ... my different class ...
render() {
const popoverExample = (
<MyPopover id="my-first-popover" title="My Title">
my text
</MyPopover >
);
return (
<OverlayTrigger trigger="click" placement="top" overlay={popoverExample}>
<Button>Click Me</Button>
</OverlayTrigger>
);
}
Now, I want to add custom props to MyPopover component like that:
my text
And to use the new props to set some things in the popover
for example -
<Popover {...this.props} className={this.getClassName()}>
{this.showTheRightText(this.props)}
</Popover>
but then I get this warning in the browser:
Warning: Unknown props popoverType on tag. Remove these props from the element.
Now, I guess that I can just remove the {...this.props} part and insert all the original props one by one without the custom props, but In this way I lose the "fade" effect and also it's an ugly way to handle this problem. Is there an easier way to do it?
Updated answer (React v16 and older):
As of React v16, you can pass custom DOM attributes to a React Component. The problem/warning generated is no longer relevant. More info.
Original answer (React v15):
The easiest solution here is to simply remove the extra prop before sending it to the Popover component, and there's a convenient solution for doing that.
export default class MyPopover extends Component {
// ...
render() {
let newProps = Object.assign({}, this.props); //shallow copy the props
delete newProps.popoverType; //remove the "illegal" prop from our copy.
return (
<Popover {...newProps} >
// ....
</Popover>
);
}
}
Obviously you can (and probably should) create that variable outside your render() function as well.
Basically you can send any props you want to your own component, but you'd have to "clean" it before passing it through. All react-bootstrap components are cleansed from "illegal" props before being passed as attributes to the DOM, however it doesn't handle any custom props that you may have provided, hence why you have to do your own bit of housekeeping.
React started throwing this warning as of version 15.2.0. Here's what the documentation says about this:
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.
[...]
To fix this, composite components should "consume" any prop that is intended for the composite component and not intended for the child component.
For further reading, check this page from the official react site.

When should I be using React.cloneElement vs this.props.children?

I am still a noob at React and in many examples on the internet, I see this variation in rendering child elements which I find confusing. Normally I see this:
class Users extends React.Component {
render() {
return (
<div>
<h2>Users</h2>
{this.props.children}
</div>
)
}
}
But then I see an example like this:
<ReactCSSTransitionGroup
component="div"
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{React.cloneElement(this.props.children, {
key: this.props.location.pathname
})}
</ReactCSSTransitionGroup>
Now I understand the api but the docs don't exactly make clear when I should be using it.
So what does one do which the other can't? Could someone explain this to me with better examples?
props.children isn't the actual children; It is the descriptor of the children. So you don't have actually anything to change; you can't change any props, or edit any functionality; you can only read from it. If you need to make any modifications you have to create new elements using React.CloneElement.
https://egghead.io/lessons/react-use-react-cloneelement-to-extend-functionality-of-children-components
An example:
main render function of a component such as App.js:
render() {
return(
<Paragraph>
<Sentence>First</Sentence>
<Sentence>Second</Sentence>
<Sentence>Third</Sentence>
</Paragraph>
)
}
now let's say you need to add an onClick to each child of Paragraph; so in your Paragraph.js you can do:
render() {
return (
<div>
{React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
onClick: this.props.onClick })
})}
</div>
)
}
then simply you can do this:
render() {
return(
<Paragraph onClick={this.onClick}>
<Sentence>First</Sentence>
<Sentence>Second</Sentence>
<Sentence>Third</Sentence>
</Paragraph>
)
}
Note: the React.Children.map function will only see the top level elements, it does not see any of the things that those elements render; meaning that you are providing the direct props to children (here the <Sentence /> elements). If you need the props to be passed down further, let's say you will have a <div></div> inside one of the <Sentence /> elements that wants to use the onClick prop then in that case you can use the Context API to do it. Make the Paragraph the provider and the Sentence elements as consumer.
Edit:
Look at Vennesa's answer instead, which is a better explanation.
Original:
First of all, the React.cloneElement example only works if your child is a single React element.
For almost everything {this.props.children} is the one you want.
Cloning is useful in some more advanced scenarios, where a parent sends in an element and the child component needs to change some props on that element or add things like ref for accessing the actual DOM element.
In the example above, the parent which gives the child does not know about the key requirement for the component, therefore it creates a copy of the element it is given and adds a key based on some unique identifier in the object. For more info on what key does: https://facebook.github.io/react/docs/multiple-components.html
In fact, React.cloneElement is not strictly associated with this.props.children.
It's useful whenever you need to clone react elements(PropTypes.element) to add/override props, without wanting the parent to have knowledge about those component internals(e.g, attaching event handlers or assigning key/ref attributes).
Also react elements are immutable.
React.cloneElement( element, [props], [...children] ) is almost equivalent to:
<element.type {...element.props} {...props}>{children}</element.type>
However, the children prop in React is especially used for containment (aka composition), pairing with React.Children API and React.cloneElement, component that uses props.children can handle more logic(e.g., state transitions, events, DOM measurements etc) internally while yielding the rendering part to wherever it's used, React Router <switch/> or compound component <select/> are some great examples.
One last thing that worth mentioning is that react elements are not restricted to props.children.
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
They can be whatever props that makes sense, the key was to define a good contract for the component, so that the consumers of it can be decoupled from the underlying implementation details, regardless whether it's using React.Children, React.cloneElement, or even React.createContext.

Resources