Tuesday, March 7, 2017

React access Dom Nodes from this.props.children

Leave a Comment

Lets say I have a Card that contains a login Form

 <Card>      <LoginForm/>  </Card> 

How do I access the nodes from the Form within the Card render function ?

<Form >   <input type="text" name="email"/>   <input type="password" name="password"/>   <input type="submit"/>  </Form> 

Because what i´d like to do is to render the submitbutton not within the props.children context but render it wrapped outside of the given child!

render () { return (   <div className="card">       <div className="inner">         {/* render Children */}         {this.props.children != undefined ?           <div className="childrenWrapper">             {this.props.children}           </div>           : ""         }        </div>        {/* render submit from login form here, not above  */   </div>) 

example expected result

There are some components which actually do what I want. For example the Tabs component from react-toolbox. They somehow manage to render whats within the Tab (children) somewhere else

Just for instance

   <Tabs index={this.state.inverseIndex} onChange={this.handleInverseTabChange} inverse>       <Tab label='First'><small>First Content</small></Tab>       <Tab label='Second'><small>Second Content</small></Tab>       <Tab label='Third'><small>Third Content</small></Tab>       <Tab label='Disabled' disabled><small>Disabled Content</small></Tab>     </Tabs> 

Which will lead to the following html rendered html example

As you can see the children from the tab where rendered within their own section

I do not want to change anything on the Form to solve this problem, I would like to pass the Form into the Card and let the Card decide how the Form will be rendered within the card render function.

Since I´m trying to implement the Google Material Design Card component and just use it as a template there are more elements coming which will need to be split up and placed at the positions I want them to be. The thing is I could actually place the relevant HTML around the Form to get it as the Card I want it to be, but than I wouldnt need the component at all.

4 Answers

Answers 1

There are some decent answers here, but none of them directly answer your question. Therefore, even though you should refactor your code (as elucidated below), I am going to provide you a working solution:

class Card extends React.Component {   constructor() {     super();     this.state = {};   }    render() {     console.log(typeof this.props.children)     return (       <div>         {typeof this.props.children === 'object'           ? React.cloneElement(this.props.children, { ref: (n) => this.form = n })           : null}         <button onClick={(e) => console.log(this.form.data)}>submit</button>       </div>     );   } }  class Form extends React.Component {   constructor() {     super();     this.onChange = this.onChange.bind(this);     this.state = {};   }    onChange(e) {     this.data = e.target.value;   }    render() {     return (       <form>         <input type="text" onChange={this.onChange} />       </form>     );   } }  ReactDOM.render(   <Card><Form /></Card>,   document.getElementById('container') ); 

https://jsbin.com/fohehogozo/edit?js,console,output

By setting a property on the instance, you can then access that property from children by using a ref. I checked for typeof === object here, because there was only one child.

WARNING: this code is NOT PRODUCTION READY. Do not ever run this in production. The code I have demonstrated is a terrible hack, and you should never try this at home.


Answers 2

If you are trying to submit a form, maybe look at passing down an onChange event and storing the value (based on the name of the field) in the state of the Card. Then attach the onChange event on the inputs so as soon as they're updated, the data will be passed back up to the container for you to submit.

Answers 3

If you would like to split up the childrens passed, you can simply filter the children array to split up the children, however your childrens seem to be nested.

Why dont you let the cards children handle the separation between your inner container and other content?

I think restructuring in this case is more suitable than modifying the passed children property.

Also, pulling the submit button out of the actual form tags, would break your form as it would no longer submit without some custom connection between the button and the actual form.

Answers 4

Don't try to manipulate the DOM; it's generally an anti-pattern in React (though there are a few valid use cases). In your case, rather than literally trying to move the elements, I'd simply hide the button in the form and add it to the parent.

Assuming you have access to the internals of <LoginForm>, you can add a prop to hide the button:

const button =   <div class="flatbuttonWrapper">     <input type="submit"/>   </div>;  <Form>   <input type="text" name="email"/>   <input type="password" name="password"/>   {!this.props.hideButton && button} </Form> 

Add the button to the Card component:

render() {   return (     <div className="card">       <div className="inner">         {this.props.children != undefined ?           <div className="childrenWrapper">             {this.props.children}           </div>           : ""         }       </div>       <div class="flatbuttonWrapper">         <input type="submit"/>       </div>     </div>   ); } 

Finally, in your parent:

<Card>   <LoginForm hideButton /> </Card> 

All that said, it really feels like you need to structure your code better and break some of these components up into smaller, more reusable pieces. For example, the Card component probably shouldn't be affecting the button's style or conditionally rendering children; it should just add a frame around any children. Then you can create a more complex component that composes these simpler sub-components to to whatever you need.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment