Render Component inside a different location - javascript

This is my react app component structure
<App>
<Body>
<Top>
### //<- Position 1
</Top>
<Middle>
<MyComponent/>
</Middle>
</Body>
</App>
I want the <MyComponent/> to render at Position 1 anytime it is imported.
I have tried ReactDom.createPortal but i keep getting Target container is not a DOM element. If this is not possible how best can i achieve a similar setup.
What I have tried
//Top.js
<Top>
<div id="top-render"/>
</Top>
//MyCustomElement.js
class MyCustomElement extends Component{
render(){
return (<div>Demo Text</div>)
}
}
export default props => ReactDOM.createPortal(
<MyCustomElement />,
document.getElementById('top-render'),
);
//Middle.js
<Middle><MyCustomElement/></Middle>

You are getting the Target container is not a DOM element error, because by the time execution reaches your ReactDOM.createPortal() call, the DOM node that document.getElementById('top-render') is attempting to retrieve, hasn't yet been written to the DOM.
This is because by that time, React is yet to make a successful pass through your component tree. i.e. React is yet to successfully create a virtual representation of everything in the component tree, and as a result, nothing has been written to DOM yet. So although you were expecting the top-render div to be there in the DOM, it hadn't actually been written yet.
The solution is to create a DOM node owned by <MyComponent, that will contain it's children (let's call this node el), then pass that DOM node, as the target DOM element in your React.createPortal() call (aka, it's second argument). Then when <MyComponent> mounts, you simply retrieve the top-render div and append el to it. Like so:
const MyCustomComponent = () => {
return (<div>Demo Text: from "MyCustomComponent"</div>)
}
class MyComponent extends React.Component {
el = document.createElement("div");
componentDidMount() {
// saving this on the component so we can access it in `componentWillUnmount` later
this.targetEl = document.getElementById("top-render");
this.targetEl.appendChild(this.el);
}
componentWillUnmount() {
this.targetEl.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(<MyCustomComponent />, this.el);
}
}
I put together a working example.
This approach works, because React guarantees that componentDidMount() will get called immediately after the component is mounted (aka. written to the DOM). In other words, we know for sure that by the time componentDidMount() gets called, our component tree has been written to the DOM at least once, and consequently, the top-render div exists in the DOM.

What do you mean by "Anytime it is imported" ?
Anyway you can just use conditional rendering. It will do the job, but it seems your intention is something else!
render(){
const { showMyComp } = this.state
return(
<App>
<Body>
<Top>
{showMyComp && (<MyComponent />)}
</Top>
<Middle>
<MyComponent/>
</Middle>
</Body>
</App>
)
}

ReactDom.createPortal is suggested to be used on DOM nodes outside the scope of React App. Inside you should declare a component normally <Top><MyComponent/></Top>.
But for curiosity's sake, in case you want to go with this approach only, you should use the ref of the DOM node in Top element.
ReactDOM.createPortal(
<MyComponent/>,
/*Ref of Container element*/,
);

Related

Why does render not erase the state of children in Reactjs?

According to the react documentation,
when setState is called by the user, render is soon after called "behind the scenes". render usually returns a JSX element (though a couple other types are allowed). My question is, since render creates a new JSX element each time, and since setState called on a certain component then calls render on the same component, how is the old state of the components children retained?
I think this question is clear in and of itself,
but to illustrate exactly what i mean i will give a toy example.
export class App extends Component {
constructor (props) {
super(props);
this.state = {
foo: [null]
};
this.addToFoo = this.addToFoo.bind(this);
}
addToFoo() {
this.setState(state => {
return {foo: [...state.foo, null]};
});
}
render() {
return (<div onClick={this.addToFoo}>{this.state.foo.map( el => (<Bar></Bar>))}</div>);
}
}
with Bar defined as
export class Bar extends Component {
constructor(props) {
super(props);
this.state = {color: "#111111"};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
this.setState(state => {
if (state.color === "#111111")
return { color: "#dddddd"}
else
return { color: "#111111"};
})
}
render() {
return (
<div style={{color: this.state.color}} onClick={this.changeColor}>
Howdy!
</div>
);
}
}
Here, when the child is clicked, it toggles its color between a light gray and a dark gray.
When the parent is clicked it changes its state by adding an element to foo.
render is then called on parent behind the scenes, which returns one JSX element that contains foo.length child components of type Bar. However, somehow, the previous state of the children is retained (meaning the colors of the previously included children stay the same, and only the new one is default constructed), even though the foo.length components were newly returned and did not reference the old children at all. How is this possible?
Sandbox
React does lots of work to check if new render returns absolutely new component, or if it is the same component. Their diffing algorithm include comparisons and some heuristics for this, but you are not required to know in details how it works, since it can change internally. In your particular case this work may result in creating absolutely new component instead of reusing previous, because you don't use keys.
More in-depth info on diffing you can read in react documentation
render() is indeed called "behind the scenes" when there is a change to the state of the parent element. But it doesn't simply instantiate all the class components again and output the result of the render() call to the browser.
Instead, React maintains a virtual DOM, a copy of the DOM visible in the browser. It compares the output of the render() call to that virtual DOM, and it is only the changes that will be output to the real DOM.
As part of carrying out this comparison, for each component it will determine if it is the same component that was there in the previous render by comparing to the virtual DOM (exactly how it does this would require a close inspection of the algorithm).
If a class component is determined to be the same component as before, it will not instantiate it again but instead call the component's render() method using the same class instance and thus the same state as before. (As this documentation explains, a class component's constructor is only called when it is mounted.)
In this way children class components can retain state.

Why can't I access children of a element rendered through React DOM?

I am trying to access the children of the measureDiv I created and appended with ReactDOM.create in the folllowing snippet, I am calling this function in a hook only on mount of the element. The problem is that the element's children are empty although it is rendered on the screen and visible in the inspector. I am able to access the children on the second render.
const containerRef = useRef<ReactNode | null>(null)
const measureElement = (element:any, layerId = "root") => {
const measureLayer = document.createElement("div");
measureLayer.setAttribute("class", "measure-div");
measureLayer.setAttribute("id", "measure-div");
ReactDOM.render(element, measureLayer);
containerRef.current?.appendChild(measureLayer);
const measureDiv = document.getElementById("measure-div");
console.log(measureDiv, measureDiv.children.item(0))
}
useEffect(() => {
measureElement(
<BrowserRouter>
<MuiThemeProvider theme={ManagementTheme}>
<Element {...{props}/>
</MuiThemeProvider>
</BrowserRouter>
)
}, [])
return (
<Grid
container
className={classes.columnContainer}
data-automation-id="test-answers-row"
id="test-answers-row"
ref={containerRef}
/>
)
My question is why can't I access the children that I render in a Node through ReactDOM.render right after I render them? And why I get them on the next render?
Incase you are wondering I am trying to render the element in a hidden div before rendering to the App to get its size to predict the layout.
From React docs:
ReactDOM.render() currently returns a reference to the root ReactComponent instance. However, using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the root ReactComponent instance, the preferred solution is to attach a callback ref to the root element.
You can check it by delaying reading childs of root (measureLayer) element.

React hook how to avoid executing child components when parent rerenders

Given the following are all written in react hooks:
export Parent = () => {
const [msgValue, setMsgValue] = useState();
....
return {
<>
...
<Child setMsgValue={setMsgValue}/>
...
</>
}
}
shouldSkipUpdate = (oldProps, newProps) => {
...
return true;
}
export Child = React.memo({setMsgValue} => {
return (
<>
<HeavyComponent1>
<HeavyComponent2>
<InputBox onChange={setMsgValue}>
</>
)
}, shouldSkipUpdate);
My problem is that the input box is not responsive, my investigation shows that every keydown, <Child> will get executed once, even shouldSkipUpdate returns true, which in turn causes <HeavyComponent1> and <HeavyComponent2> code get executed and causes lagging.
What have I done wrong and how do I actually prevent <HeavyComponent> gets executed?
I am also a bit confused about re-render vs the component code gets executed, would be great to get clarification on this as well.
Please check below codesanbox, i have a child component which does not get rendered even if parent component gets rendered. Hope tha gives you some clarity on what needs to be done.
https://codesandbox.io/s/friendly-cerf-qme94
If the shouldSkipUpdate returns true the component doesn't rerender. Is it possible that you have HeavyComponent1 and/or HeavyComponent2 somewhere else in the tree, and those component instances get executed and rendered?
I just realised that InputBox is being wrapped by formsy which somehow updates the parent component which causes the rerender of everything.

One time action in React Components

I have a question regarding "one time actions" in react components. Imagine for example I want to scroll some element to certain position, or reset the internal react state.
So far I've been doing this by using a combination of a boolean flag (e.g. doAction: true) and an update action (e.g. setDoActionBackToFalse), but this seems too complex. Does anyone have any nice solution to this?
Note: The action can actually happen multiple times during the lifetime of the component but each time it has to be specifically triggered and happen only once (not keep happening on every rerender). E.g. scroll to every newly added item in scrollpane.
I created small fiddle to make the problem more obvious:
https://jsfiddle.net/martinkadlec/et74rkLk/1/
This uses the boolean flag approach.
It has been some time since I asked this question and since then I found that as long as the "one time action" doesn't actually rerender the component, but instead just modifies some browser state (e.g. focus, scroll position, etc.) people generally tend to solve this by having a class method and calling it from the parent component using refs.
To illustrate on the focus example:
class Input extends React.Component {
inputElRef = React.createRef();
focus = () => this.inputElRef.current.focus();
render() {
return (
<input ref={this.inputElRef} />
);
}
}
class Parent extends React.Component {
inputRef = React.createRef();
render() {
return (
<div>
<button onClick={() => this.inputRef.current.focus()}>Focus input</button>
<Input ref={this.inputRef} />
</div>
);
}
}
I think that you can use componentDidMount lifecycle hook. This hook is invoked only once immediately after a component is mounted and the DOM can be accessed in it.
You can also call your 'one time action' in component constructor but it's called before component is mounted and before initial render so you can't access DOM there.
So you can initialize component state in a constructor (according to React docs: constructor is the right place to initialize state) but you can't scroll some element to certain position in constructor because you can't access component DOM elements in it.
Summing up: state initialization should be done in constructor while 'one time actions' manipulating DOM should be done in componentDidMount.
Wrap your action handlers inside a higher order function which invokes them only once. Lodash has once. Ramda has it too.
Updates for your scrolling scenario.... Scrolling is a side effect which must be initiated by the DOM API. You can write an HOC which wraps any component inside it -
function OnFocusExtender(Wrapped){
return class ExtendedFocus{
focus = _.once(elem => elem && elem.focus && elem.focus());
render(){
return <Wrapped ref={this.focus} {...this.props} />;
}
}
}
Then you can use it in your code like -
render(){
let FocusedComponent = FocusExtender(YourComponent);
return <FocusedComponent a={"blah"} b={blah} />
}
Updated for a generic side-effects approach:
The HOC:
function WelcomingParty(...party)=>(Wrapped)=>{
return class ExtendWelcome{
// Every host in the welcoming party greets
// the guest :)
welcome = (ref) => party.forEach(host => host(ref));
render(){
return <Wrapped ref={this.welcome} {...this.props} />;
}
}
}
Usage:
let hostFn = (fn)=>(ref)=> ref && (typeof ref[fn] == "function") && ref[fn](),
hosts = ["focus", "scrollIntoView"].map(hostFn);
render(){
let Component = WelcomingParty(...hosts)(YourComponent);
return <Component a={"blah"} b={blah} />
}

React, how to access the DOM element in my render function from the same component

I'm wondering what's the best practice for accessing the DOM elements inside my render function from the same component. Note that I will be rendering this component multiple times on a page.
e.g.
var TodoItem = React.createClass({
...
render:function(){
function oneSecLater(){
setTimeout(function(){
//select the current className? this doesn't work, but it gives you an idea what I want.
document.getElementsByClassName('name').style.backgroundColor = "red";
)}, 1000);
}
return(
<div className='name'>{this.oneSecLater}</div>
)
})
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. It's better to use a state variable and called the setState method to re-render the DOM.
Here, no need to use setTimeout. There are lifecycle methods for component, of which componentDidMount is called after the render. So, you can get the reference to your div in the method.
var TodoItem = React.createClass({
...
componentDidMount: function () {
if(this.myDiv) {
this.myDiv.style.backgroundColor = "red";
}
}
render:function(){
return(
<div className='name' ref = {c => this.myDiv = c}></div>
);
});
You can make use of ref callback to access the dom element in react, which is what React Docs recommend to follow
and do that in the componentDidMount lifecycle function as refs won't be accessible before the DOM is created
var TodoItem = React.createClass({
...
componentDidMount() {
setTimeout(function(){
this.myDiv.style.backgroundColor = "red";
)}, 1000);
}
render:function(){
return(
<div className='name' ref={(ele) => this.myDiv = ele}></div>
)
})
DOCS
You should avoid accessing DOM element because the whole point of react is putting abstraction over dom. React keeps representation of DOM in memory which is referred as VirtualDom. Working with VirtualDom will make unit testing your application is easier.If you really know what you are doing, here is how to do it:
componentDidMount(){
const name=this.name.current.style() //current will return the actual DOM
element
}
name=React.createRef() //create a ref object
<div ref={this.name} className="anything" /> //your classname does not need to be named as "name". You access the element via the "ref" attribute not the classname.
In ComponentDidMount, when your component is mounted its style change will be applied.
Just came across this after trying to do form validation before opening a stripe checkout modal with React 14.
I would like to note that you're not actually accessing a DOM Element with references. You're simply accessing the React Component Object. Shown here:
The top one is calling ref.ticketForm, the bottom is showing document.getElementById('ticketform').
The reason I needed to do this was the following:
<Button color="success" size="lg" type="button" onClick={(e) => {
const ticketForm = document.getElementById('ticketForm');
const isValid = ticketForm.reportValidity();
if (!isValid) e.stopPropagation();
}}>Buy Tickets</Button>
reportValidity() is a DOM Element method: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reportValidity
Referencing this issue, this person showed this method being used on a reference object, which naturally isn't correct: https://github.com/azmenak/react-stripe-checkout/issues/121#issuecomment-431635855
Hopefully this clears up that DOM Elements do not explicitly equal React Components. If you need to manipulate the DOM in any way, do it in a react way first. This is an edge case where I would rather rely on form validation for a dynamic form than manually doing very heavy custom validation.
here is my solution:
To get a computedCss of an specific element, you need to add a ref attribute to the elemenet first.
enter image description here
render(){
<div>
<Row className="chartline2">
<Col className="gutter-row" span={24}>
<div className="gutter-box lineChartWrap" id="lineChartWrap" ref="lineChartWrap">
<LineChart data={lineChartData} options={lineChartOptions} width="100%" height="300"/>
</div>
</Col>
</Row>
</div>
}
And then, in the componentDidUpdate() function, get your element's css by using window.getComputedStyle(this.refs.lineChartWrap).XXX
enter image description here
componentDidUpdate(){
console.log("------- get width ---");
let ele = document.querySelector("#lineCharWrap");
console.log(this.refs.lineChartWrap);
console.log(window.getComputedStyle(this.refs.lineChartWrap).width);
}

Categories

Resources