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}
Related
We are wrapping a component library with React components, but in some cases the library manipulates the DOM tree in such a way that React will crash when trying to remove the React components.
Here's a sample that reproduces the issue:
function Sample ()
{
let [shouldRender, setShouldRender] = React.useState(true);
return (
<React.Fragment>
<button onClick={() => setShouldRender(!shouldRender)}>show/hide</button>
{ shouldRender && <Component /> }
</React.Fragment>
);
}
function Component ()
{
let ref = React.useRef();
React.useEffect(() => {
let divElement = ref.current;
someExternalLibrary.setup(divElement);
return () => someExternalLibrary.cleanup(divElement);
});
return <div ref={ref} id="div1">Hello world</div>;
}
ReactDOM.render(
<Sample />,
document.getElementById('container')
);
let someExternalLibrary = {
setup: function(divElement)
{
let beacon = document.createElement('div');
beacon.id = `beacon${divElement.id}`;
divElement.parentElement.replaceChild(beacon, divElement);
document.body.append(divElement);
},
cleanup: function(divElement)
{
let beacon = document.getElementById(`beacon${divElement.id}`);
beacon.parentElement.replaceChild(divElement, beacon);
}
}
You can find this sample on JSFiddle.
The above sample will render the Component which integrates with someExternalLibrary.
The external library moves the elements from inside the React component somewhere else.
Even if the external library puts back the element at its original location using a beacon, React will still complain when trying to remove the Component when you click on the show/hide button.
This will be the error
"Error: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
at removeChildFromContainer (https://unpkg.com/react-dom#17/umd/react-dom.development.js:10337:17)
at unmountHostComponents (https://unpkg.com/react-dom#17/umd/react-dom.development.js:21324:11)
at commitDeletion (https://unpkg.com/react-dom#17/umd/react-dom.development.js:21377:7)
at commitMutationEffects (https://unpkg.com/react-dom#17/umd/react-dom.development.js:23437:13)
at HTMLUnknownElement.callCallback (https://unpkg.com/react-dom#17/umd/react-dom.development.js:3942:16)
at Object.invokeGuardedCallbackDev (https://unpkg.com/react-dom#17/umd/react-dom.development.js:3991:18)
at invokeGuardedCallback (https://unpkg.com/react-dom#17/umd/react-dom.development.js:4053:33)
at commitRootImpl (https://unpkg.com/react-dom#17/umd/react-dom.development.js:23151:11)
at unstable_runWithPriority (https://unpkg.com/react#17/umd/react.development.js:2764:14)
at runWithPriority$1 (https://unpkg.com/react-dom#17/umd/react-dom.development.js:11306:12)"
An easy fix would be to wrap the existing HTML inside another DIV element so that becomes the root of the component, but unfortunately, that's not always possible in our project, so I need another solution.
What would be the best approach to solving this?
Is there a way to use a ReactFragment and re-associate the HTMLElement with the fragment during cleanup?
The cleanup function of useEffect, hence someExternalLibrary.cleanup(), is called after React has updated the DOM (removed the div from the DOM). It fails, because React is trying to remove a DOM node that someExternalLibrary.setup() removed (and someExternalLibrary.cleanup() will put back later).
In class components you can call someExternalLibrary.cleanup() before React updates the DOM. This would fix your code:
class Component extends React.Component {
constructor(props) {
super(props);
this.divElementRef = React.createRef();
}
componentDidMount() {
someExternalLibrary.setup(this.divElementRef.current);
}
componentWillUnmount() {
someExternalLibrary.cleanup(this.divElementRef.current);
}
render() {
return <div ref={this.divElementRef} id="div1">Hello world</div>;
}
}
The "extra div solution" fails for the same reason: Calling someExternalLibrary.cleanup() during the cleanup of useEffect() will mean the DOM has changed, but someExternalLibrary expects no change in the DOM. By using the class component with componentWillUnmount, someExternalLibrary.setup() and someExternalLibrary.cleanup() will work with the same DOM.
React doesn't expect you to remove nodes that React has created. Treat nodes created by React as read-only. Instead you should:
wrap the existing HTML inside another DIV element
However you don't explain why that isn't feasible:
but unfortunately, that's not always possible in our project, so I need another solution.
Without that info, which is your real issue, a solution cannot be given.
What's the difference between createRef and ref={(c) => this.el = c}?
When I output each ref has same element but it not false.
why?
import React from "react"
class Home extends React.Component {
constructor(){
super();
this.el1 = React.createRef();
}
componentDidmount(){
console.log(el1 === el2) // false why false?
}
render(){
return (
<>
<div ref={this.el1}>
<span>A</span>
</div>
<div ref={(c)=> { this.el2 = c }}}>
<span>A</span>
</div>
</>
)
}
In the code both ref are pointing to two different DOMnodes that's why these are not same.
createRef is returning either a DOM node or a mounted instance of a component, depending on where you call it. Either way, what you have in hand is indeed straightforward as you've noted. But what if you want to do something with that reference? What if you want to do it when the component mounts?
Ref callbacks are great for that because they are invoked before componentDidMount and componentDidUpdate. This is how you get more fine-grained control over the ref. You are now not just grabbing DOM elements imperatively, but instead dynamically updating the DOM in the React lifecycle, but with fine-grained access to your DOM via the ref API.
I can define a component like this:
class Welcome extends React.Component {
render() {
return <h1>Hello!</h1>;
}
}
When I want to render this object in the Dom (or rather add it to the virtual DOM) I call
ReactDOM.render(
<Welcome />,
document.getElementById('root')
);
That means that at some point multiple .render() functions are nested inside of each other since we defined return <h1>Hello!</h1>; inside of the .render() function of Welcome.
It also means that I can use the .render() method to render an Object to the DOM and to define a new react object.
This syntax is from the official documentation
Does .render() just render things to the virtual DOM and the nesting is resolved by React internally or is there more behind it?
From Rendering a Component:
(updated to reflect your code)
class Welcome extends React.Component {
render() {
return <h1>Hello!</h1>;
}
}
ReactDOM.render(
<Welcome />,
document.getElementById('root')
);
We call ReactDOM.render() with the <Welcome /> element.
React calls the Welcome component with {} as the props.
Our Welcome component returns a <h1>Hello!</h1> element as the result.
React DOM efficiently updates the DOM to match <h1>Hello</h1>.
It may be beneficial to read up on the React Life Cycle.
To understand render you have to understand how react works internally. To understand this in greater detail I would recoment reading this but here is a quick overview:
Conceptually a react component is a just a function that returns an Object like this:
{
type: 'button',
props: {...}
};
So the render() part of a class component just specifies what part of the component will be returned. The react virtual dom is made up of many of these object nested inside of each other (in props.children). React will start at the top object, turn it into an html node and render it to the dom, then do the same for all of its children etc.
I have a gallery component that takes in an array of components. In each of the child components I am assigning a ref. The reason for this is because within the child component there are many other children components and I am attempting to access some functions on a component that is about 5 component deep. The below code shows the initial setup:
export class Gallery extends React.Component {
render() {
const galleryItems = data.map((item, index) => {
return (
<GalleryItem
ref={React.createRef()}
/>
);
});
return (
<div >
<Gallery
items={heroGalleryItems}
/>
</div>
);
}
}
When the Gallery component renders all the refs in the array of GalleryItem component are correct. But as soon as the Gallery component re renders for any reason the refs in the GalleryItem components become null values.
I have tried several things in the children components but nothing I do fixes the issue. I believe the reason is because something is happening in the code above.
I have also tried to change up the code after reading the following:
Issue storing ref elements in loop
However its not really clear to me what the person is saying to do when I look at my own implementation.
You need to move out React.createRef() from the loop (and also render) as it is creating a new ref on every render.
Depending on your code/usage, you'd need to do this in constructor and CWRP methods (basically whenever data changes).
Then creating galleryItems would be like
...
<GalleryItem ref={item.ref} />
...
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.