How can I append external DOM to React component? - javascript

I have a page with a form rendered in the server, it handles validation, and the correct value for the selects.
I want to hide the DOM of that form, and append it into a react component so I can use it in react-router.
const NewItem = React.createClass({
render() {
return (
<div>
<h1>New item</h1>
{/* I WANT THE FORM FROM THE PAGE HERE*/}
</div>
)
}
})
What is the best way to do it?

You have full access to the DOM in componentDidMount. You can use refs to access the specific DOM element you want.
var NewItem = React.createClass({
componentDidMount: function () {
this.refs.formTarget.appendChild(myFormDomElement);
},
render: function () {
return React.DOM.div(null,
React.DOM.h1(null, "New item"),
React.DOM.div({ref: "formTarget"}));
}
});
Note that in 0.14, a ref is a raw DOM element. Prior to that a ref was a react component and you had to call React.findDOMNode(it) to get the actual DOM element.

React > 16.3
Try using portals like in this component:
import {Component} from 'react';
import {createPortal} from 'react-dom';
import PropTypes from 'prop-types';
class DOMPortal extends Component {
constructor(props) {
super(props);
this.el = document.createElement(props.component);
}
componentDidMount() {
this.props.parentEl.appendChild(this.el);
}
componentWillUnmount() {
this.props.parentEl.removeChild(this.el);
}
render() {
return createPortal(
this.props.children,
this.el,
);
}
}
DOMPortal.propTypes = {
parentEl: PropTypes.object.isRequired,
component: PropTypes.string,
};
DOMPortal.defaultProps = {
component: 'div',
};
Now you can pass your external DOM reference as the parentEl props to it:
<DOMPortal parentEl={decorator.contentWidget.domNode}>...children</DOMPortal>
React < 16.3
Using this.refs is "deprecated", try this instead :
render() {
return <div ref={(DOMNodeRef) => {
this.componentRef=DOMNodeRef;
}}>
...
</div>;
}
Then this.componentRef will be accesible in componentDidMount() so you can append your external DOM element:
componentDidMount(){
this.componentRef.appendChild(externalDOMelement);
}
Notes:
Remember that this.componentRef changes over time (renders()), so you must update it wherever you are passing it to.
Check for a defined reference before using it: if(this.componentRef){// ... your code}
Functional Components' refs are handled differently.
Source:
React Doc

React gives us the functionality dangerouslySetInnerHTML. it gives us the support of adding HTML element. for example
function createMarkup() {
return {__html: 'First ยท Second'};
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
You can also use portals introduced in react-16 for appending React component anywhere in the tree by using following code.
ReactDOM.createPortal(child, container)
follow following link Portals for refference

Related

Manage Focus on stateless Components - React

Having a container Component which hold state. it renders a number of stateless components.
I want to get access to all of their DOM nodes, so i can call the focus method on demand.
I am trying the ref approach as it is encouraged by the react documentation.
I'm getting the following error:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
What is the recommended way to get around this error?
preferably, without extra dom elements wrappers like exra divs.
Here is my Current Code:
Container Component - responsible for rendering the stateless components.
import React, { Component } from 'react';
import StatelessComponent from './components/stateless-component.jsx'
class Container extends Component {
constructor() {
super()
this.focusOnFirst = this.focusOnFirst.bind(this)
this.state = {
words: [
'hello',
'goodbye',
'see ya'
]
}
}
focusOnFirst() {
this.node1.focus()
}
render() {
return (
<div>
{
this.state.words.map((word,index)=>{
return <StatelessComponent
value={word}
ref={node => this[`node${index}`] = node}/>
})
}
<button onClick={this.focusOnFirst}>Focus on First Stateless Component</button>
</div>
)
}
}
Stateless Component - for sake of simplicity, just display a text inside a div.
import React from 'react';
export default function StatelessComponent(props) {
return <div>props.value</div>
}
Stateless (functional) components can't expose refs directly. However, if their internal components can use refs, you can pass a function from the parent (the grandparent) of the stateless component (the parent) as a ref via ref forwarding. Use the function as the ref target of the DOM element. Now the grandparent has direct access to the DOM element ref.
See Exposing DOM Refs to Parent Components in React's documentation.
const StatelessComponent = React.forwardRef((props, ref) => (
<div>
<input ref={ref} {...props} />
</div>
));
class Container extends React.Component {
itemRefs = []
componentDidMount() {
this.focusOnFirst();
}
focusOnFirst = () => this.itemRefs[0].focus()
inputRef = (ref) => this.itemRefs.push(ref)
render() {
return (
<div>
<StatelessComponent ref={this.inputRef} />
<StatelessComponent ref={this.inputRef} />
</div>
)
}
}
ReactDOM.render(
<Container />,
demo
)
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
Try this, basically you pass a callback as ref to the stateless components that gets the input instance an adds it to an array owned by the container
class Container extends Component {
constructor() {
super()
this._inputs = [];
this.focusOnFirst = this.focusOnFirst.bind(this)
this.state = {
words: [
'hello',
'goodbye',
'see ya'
]
}
}
focusOnFirst() {
this._inputs[0].focus()
}
refCallback(ref) {
this._inputs.push(ref)
}
render() {
return (
<div>
{
this.state.words.map((word,index)=>{
return <StatelessComponent
value={word}
refCallback={this.refCallback}/>
})
}
<button onClick={this.focusOnFirst}>Focus on First Stateless Component</button>
</div>
)
}
}
And the stateless get modified a little too
function StatelessComponent({refCallback, value}) {
return <input ref={refCallback} value={value}/>
}
Here's a working plunker

React Executing a method inside a component

I have a component like this:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props, context) {
super(props, context);
this.state = {
isActive: false,
}
}
showMyComponent() {
this.setState({
isActive: true,
});
}
hideMyComponent() {
this.setState({
isActive: false,
});
}
render() {
return (
<div>
<h1>Compoent Here</h1>
</div>
);
}
}
export default MyComponent;
Now, on my index.js I am adding several components.
...
<Header />
<Nave />
Can I now do something like this here:
MyComponent.showMyComponent();
Like you normally call a function?
If not, how is this done?
You can use references. In your render() method you can get the ref. e.g.
<MyComponent ref={ref => {this.myComponent = ref}}/>
You need to create a field myComponent and assign it to it. With that you can call it like this.myComponent.showMyComponent()
See here Refs and the DOM
Use State
You are thinking about react wrong. You should not have to call a components function like this ever.
You can pass a prop to the component that will make the component hide or show.
or wrap the component in a if in the parent. Use the parents state to hide or show the component.
Like
if (someCondition) {
<MyComponent />
}
It's doable, even if some people hates this option, cause it's not the official React way, true.
You can define any public method on your component classes (such as a reset method on a Typeahead) and call those public methods through refs (such as this.refs.myTypeahead.reset()). In most cases, it's clearer to use the built-in React data flow instead of using refs imperatively.
But However, thinking out of the box, is not forbidden so you can use refs for this.
class Parent extends Component {
onSomeThing() {
// Call some method of myChild
this.myChild.myChildsPublicMethod()
}
render() {
return <MyChild ref={ref => { this.myChild = ref; }} />
}
}
// MyChild
// Just as demo using Pure components here.
// You could use the normal class notation..
const MyChild = () => <div>Ola</div>;
MyChild.someMethod = () => console.log('Ola');
More here https://zhenyong.github.io/react/docs/more-about-refs.html

Is ReactDOM.findDOMNode required in this case?

I've taken next code from here: https://www.meteor.com/tutorials/react/adding-user-accounts.
Can I replace in this particular case
ReactDOM.findDOMNode(this.refs.container)) with
this.refs.container
without any hidden bugs in future?
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
export default class AccountsUIWrapper extends Component {
componentDidMount() {
// Use Meteor Blaze to render login buttons
this.view = Blaze.render(Template.loginButtons,
ReactDOM.findDOMNode(this.refs.container));
}
componentWillUnmount() {
// Clean up Blaze view
Blaze.remove(this.view);
}
render() {
// Just render a placeholder container that will be filled in
return <span ref="container" />;
}
}
Or maybe even change using callback function:
....
export default class AccountsUIWrapper extends Component {
componentDidMount() {
// Use Meteor Blaze to render login buttons
this.view = Blaze.render(Template.loginButtons,
this.container);
}
....
render() {
// Just render a placeholder container that will be filled in
return <span ref={(node) => (this.container = node) />;
}
}
As suggested in the react refs documentation
If you worked with React before, you might be familiar with an older
API where the ref attribute is a string, like "textInput", and the DOM
node is accessed as this.refs.textInput. We advise against it because
string refs have some issues, are considered legacy, and are likely to
be removed in one of the future releases. If you're currently using
this.refs.textInput to access refs, we recommend the callback pattern
instead.
Hence ref callback is the right way to go if you want to have future support
export default class AccountsUIWrapper extends Component {
componentDidMount() {
this.view = Blaze.render(Template.loginButtons,
this.container);
}
....
render() {
return <span ref={(node) => (this.container = node) />;
}
}

Why isn't a DOM node rendered by React considered a node by React.PropTypes.node validation?

Let's say I have two components:
An overlay trigger:
import {openOverlay} from './overlay-actions'
class OverlayTrigger extends Component {
handleMouseOver(event) {
// updates global store
openOverlay({
triggerNode: this.refs.container
})
}
render() {
return <div ref="container" onMouseOver={this.handleMouseOver.bind(this)}></div>;
}
}
// ...map actions to dispatch here
export default OverlayTrigger;
And the overlay container component which gets its props from a parent component that watches a global store for the active overlay definition, which has a triggerNode prop.
class OverlayContainer extends Component {
render() {
return <div></div>;
}
}
OverlayContainer.propTypes = {
triggerNode: PropTypes.node
}
export default OverlayContainer;
The result here is that triggerNode as passed to OverlayContainer is a valid DOM node, but fails the validation by ReactPropTypes' isNode validator. Why? And how can this be resolved?
Use
triggerNode: React.PropTypes.instanceOf(Element)
to verify DOM elements.

React: Can I add attributes to children's resultant HTML?

I have a component that wraps other components:
class MyComp extends React.Component {
render() {
return <div> {this.props.children} </div>
}
}
Let's say I add another random component as a child:
<MyComp><FancyP>Hello</FancyP></MyComp>
The resultant HTML would be like this:
<div>
<p class="fancy">Hello</p>
</div>
I know that using React.Children I could add new props to the child component, but what I really want to do is add custom attributes to the resultant HTML of any random child, having something like this:
<MyComp childAttr={{'data-x':'value'}}><FancyP>Hello</FancyP></MyComp>
that would generate the following:
<div>
<p class="fancy" data-x="value">Hello</p>
</div>
Is there a way to achieve this? Adding a props to the children does not work because children's classes are not aware of the new props and they ignore them.
Not sure why you need this and I do recommend you to rethink your architecture before it is too late.
But you can do a little bit of a hackery using ReactDOM.findDOMNode.
First you need to set refs for every child component. By cloning element and assigning a ref.
Then attach hooks on componentDidMount and componentDidUpdate events, find dom element using findDOMNode and manually populate dataset. DEMO.
import React, { Component, Children, cloneElement } from 'react'
import { render, findDOMNode } from 'react-dom'
class MyComp extends Component {
componentDidMount() {
this.setChildAttrs()
}
componentDidUpdate() {
this.setChildAttr()
}
setChildAttrs() {
const { childAttrs } = this.props
const setAttrs = el => Object.keys(childAttrs)
.forEach(attr => el.dataset[attr] = childAttrs[attr])
// for each child ref find DOM node and set attrs
Object.keys(this.refs).forEach(ref => setAttrs(findDOMNode(this.refs[ref])))
}
render() {
const { children } = this.props
return (<div> {
Children.map(children, (child, idx) => {
const ref = `child${idx}`
return cloneElement(child, { ref });
})} </div>)
}
}

Categories