How to find the DOM element of a functional children in React? - javascript

Apparently this is not possible, but since I'm not a React expert I'd like to get a confirmation.
The usual way of getting the DOM element of a child is with:
render () {
const child = React.Children.only(this.props.children);
const cloned = React.cloneElement(child, {
ref: this.someRef
});
return cloned;
}
The problem here is that according to the docs:
You may not use the ref attribute on function components because they
don’t have instances
Another option to find the DOM element could be using React.findDOMNode() but according to a comment from this issue when trying to React.findDOMNode(child):
If you want to find one of your children's DOM nodes, you have to add
a ref first (using React.cloneElement) and then use that ref.
And we're back to the previous problem.
So, is it possible to find the DOM element of a functional component?

As suggested in this question, this may be one of few acceptable cases for calling stateless component function directly:
function withRef(SFC) {
return React.forwardRef((props, ref) => SFC({ref, ...props}));
}
...
render () {
const child = React.Children.only(this.props.children);
const isFunctional = typeof child === 'function' &&
(!child.prototype || !child.prototype.isReactComponent);
if (isFunctional) {
const Child = withRef(child.type);
return <Child {...child.props} ref={this.someRef}/>;
} else {
...
}
}
This won't work if functional component's child cannot accept a ref, too.
In this form it's inefficient because a child will be re-rendered. Depending on what's the purpose, there may be better ways to achieve this. A component could be designed to receive a component to render as a prop instead of a child. Or if DOM element is needed for specific task like setting up DOM event listener, this may be done like in this case .

Related

Force re-render React component when modified element HTML attribute

I have a button component rendered like below. I can't modify the button component itself but I can modify the parent component that renders it. I want to make it so that when I modify the button element's disabled attribute elsewhere in the code (that is modify the DOM like button.disabled = true) Given that I can't pass it from parent props, the button component gets re-rendered. I tried to use useRef and useEffect hook but it didn't work. I think I used them wrongly. Is there anyway I can achieve what I want?
const elementRef = useRef()
const [disabled, setDisabled] = useState(elementRef.current?.disabled)
useEffect(() => {
const buttonElement = elementRef.current
buttonElement.addEventListener('disabled', handleDisabled)
}, [elementRef.current])
const handleDisabled = (e: any) => {
setDisabled(e.target?.disabled)
}
return( <Button ref={elementRef} disabled={props.isDisabled || disabled}></Button> )
You seemingly are working in two different "worlds": ReactJs and the DOM.
I suggest to use only React, and never modify any DOM properties directly.
You don't show how you want to change the disabled attribute/property "elsewhere in the code", but I assume you want to do something like
const setMyButtonDisabled = function(){
document.querySelector('#myButton').disabled = true;
}
React is simply not able to know about this change. You have to tell React explicitly, like:
// not recommended:
const setMyButtonDisabled = function( setButtonDisabledCallback ){
document.querySelector('#myButton').disabled = true;
setButtonDisabledCallback( true );
}
And then find a way to pass the props around, so that the desired components have it
(I can't know the relation between your code example and the "elsewhere").
But ideally you would never set the DOM buttonElements .disabled property, but set a React state buttonDisabled instead,
and then just pass that around to where ever you need it:
// recommended:
const setMyButtonDisabled = function( setButtonDisabledCallback ){
setButtonDisabledCallback( true );
}

React createRef() vs callback refs. Is there any advantage of using one over the other?

I have started working on React recently and understood how refs can be used to get hold of a DOM node. In the React docs, they mention the two approaches of creating Refs. Can you please let me know in what situation a callback ref is better than createRef()? I find createRef to be simpler. Although the docs say "callback refs give you more fine grain control" I can't understand in what way.
Thank you
Besides what jmargolisvt said, one thing I found callback is very interesting that I can set multiple refs in an array so that I can control it better.
Like this:
class A extends React.Component {
constructor(props) {
super(props);
this.inputs = [];
}
render() {
return [0, 1, 2, 3].map((key, index) => (
<Input
key={key}
ref={input => this.inputs[index] = input}
/>)
);
}
}
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.
In terms of use cases, callback refs can do anything createRef can do, but not vice versa. createRef gives us a simplified syntax, but that's it.
Things you can't do with createRef:
React to a ref being set or cleared
Use an externally and internally provided ref on the same React element at the same time. (e.g. you need to measure a DOM element's clientHeight whilst, at the same time, allowing an externally provided ref (via forwardRef) to be attached to it.)
Practically you will see no difference except callback ref returns null before initial rendering.
This answer is a little biased on React-Native but still it is applicable if a React component similar to the following example.
<Animated.View> is a wrapper component for <View> that can be animated.
However if you want to access the <View> directly for something like calling the measure() method, then you can do it like:
interface State {
ref: View;
}
public render() {
<Animated.View ref={(component) => {
if (component !== null) {
this.state.ref = component.getNode();
}
}}
>
...
</Animated.View>
}
Otherwise, you need to do: this.state.ref.getNode().
TL;DR: you have control of what to do with an element or how to store it.
If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn’t matter in most cases.

Reactjs: how to write a method to handle component creation and unmount

So let's say there is acomponent which displays 2 child components: a document list and the selected document. By default the selected document component is not rendered, only when a document is selected from the list. And i also want this whole thing work when a new document is selected from the list.
There is a state which holds the document content and responsible for the selected document rendering, so i thought i'm going to set it to null in the method which handles the list item selection in order to unmount the previously created child component. Like this (excerpts from the parent class):
handleResultListItemClick(docname) {
if (this.state.sectioncontainer != null) this.setState({sectioncontainer: null},()=>{console.log("muhoo");});
var selected_doc = this.state.resultlist.filter((doc) => {
return docname === doc.properties.title;
});
this.setState({sectioncontainer: selected_doc[0].content.sections},()=>{console.log("boohoo");});
}
...
render() {
return (
...
{this.state.sectioncontainer != null && <SectionContainer listOfSections={this.state.sectioncontainer}/>}
);
}
The only problem is that state handling is not fast enough (or something) in react, because putting the state nullification and its new value setting in the same method results in no change in ReactDOM.
With the above code, the component will be created when the parent component first rendered, but after selecting a new doc in the list results in no change.
How should i implement this in way which works and also elegant?
I found this: ReactDOM.unmountComponentAtNode(container) in the official react docs. Is this the only way? If yes, how could i get this container 'name'?
Edit:
Based on the answers and thinking the problem a bit more through, i have to explain more of the context.
As kingdaro explained, i understand why there is no need to unmount a child component on a basic level, but maybe my problem is bit more sophisticated. So why did i want to unmount the child?
The documents consist of several subsections, hence the document object which is passed to the child component is an array of objects. And the document is generated dynamically based on this array the following way (excerpt from the SectionContainer class which is responsible to display the document):
buildSectionContainer() {
return this.props.listOfSections.map((section, index) =>
{
if (section.type === 'editor') return (
<QuillEditor
key={index}
id={section.id}
modules={modules}
defaultValue={section.content}
placeholder={section.placeholder}
/>
);
else if (section.type === 'text') return (
<div key={index}>{section.value}</div>
);
}
);
}
render() {
return (
<div>
{this.buildSectionContainer()}
</div>
);
}
The SectionContainer gets the array of objects and generate the document from it according to the type of these sections. The problem is that these sections are not updated when a different doc is selected in the parent component. I see change only when a bigger length array is passed to the child component. Like the firstly selected doc had an array of 2 elements, and then the newly selected doc had 3 elements array of sections and this third section is added to the previously existing 2, but the first 2 sections remained as they were.
And that’s why i though it’s better to unmount the child component and create a new one.
Surely it can happen that i miss something fundamental here again. Maybe related to how react handles lists. I just dont know what.
Edit2:
Ok, figured out that there is a problem with how i use the QuillEditor component. I just dont know what. :) The document updates, only the content of QuillEditors doesnt.
The reason your current solution doesn't actually do anything is because React's state updates are batched, such that, when setState is called a bunch of times in one go, React "combines" the result of all of them. It's not as much of a problem with being "not fast enough" as it is React performing only the work that is necessary.
// this...
this.setState({ message: 'hello', secret: 123 })
this.setState({ message: 'world' })
// ...becomes this
this.setState({ message: 'world', secret: 123 })
This behavior doesn't really have much to do with the problem at hand, though. As long as your UI is a direct translation of state -> view, the UI should simply update in accordance to the state.
class Example extends React.Component {
state = {
documentList: [], // assuming this comes from the server
document: null,
}
// consider making this function accept a document object instead,
// then you could leave out the .find(...) call
handleDocumentSelection = documentName => {
const document = this.state.documentList.find(doc => doc.name === documentName)
this.setState({ document })
}
render() {
const { document } = this.state
return (
<div>
<DocumentList
documents={this.state.documentList}
onDocumentSelection={this.handleDocumentSelection}
/>
{/*
consider having this component accept the entire document
to make it a little cleaner
*/}
{document && <DocumentViewer document={document.content.sections} />}
</div>
)
}
}

React: How to check the type of a child in the parent's render function?

I know that during render I can get the child through refs and, for example, call a function on the child (which I can add to the child for this purpose) to determine the type of the child.
<Child ref={(child) => {this._childType = child.myFunctionToGetTheType();}} />
But in this example the function isn't actually called until the Child is mounted, so after the render of the Parent has finished executing.
I have a parent component that receives its children through props. Because of React limitations I need to treat a specific child in a special way BEFORE the render of the parent has finished executing (i.e. return something else from the parent's render function for that specific child).
Is it possible to determine the type of the child before returning from the parent's render function (i.e. without using refs)?
I've had the same issue, where I was relying on child.type.name to determine the type of component. While this works fine for me, the issue is that older browsers somehow do not support that so I had to find another way. I was using Stateless Functional Components and did not want to switch away, so ended up exploiting props
const MySFC = () => {
//...
return (
<div className="MySFC"></div>
);
};
MySFC.propTypes = {
type: PropTypes.string
};
MySFC.defaultProps = {
type: "MySFC"
}
export default MySFC;
then instead of using child.type.name === 'MySFC' I used child.props.type === 'MySFC'
not ideal, but works
I have added a static function to the class that extends React.Element and it seems that I am able to access it through child.type.theStaticFunction. That probably still isn't using the React API correctly but at least it works after the code has been minified (child.type.name didn't work because the minifier was replacing class names with shorter versions).
export default class MyType extends React.Component {
static isMyType() {
return true;
}
}
then when processing the children in render
static _isChildOfMyType(child) {
const isMyType = child.type && child.type.isMyType && elem.type.isMyType();
return !!isMyType;
}

React DnD: Avoid using findDOMNode

I don't fully understand it but apparently it isn't recommended to use findDOMNode().
I'm trying to create drag and drop component but I'm not sure how I should access refs from the component variable. This is an example of what I currently have:
const cardTarget = {
hover(props, monitor, component) {
...
// Determine rectangle on screen
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
...
}
}
Source
Edit
It might be caused by my component being both the drag and drop source and target as I can get it to work in this example but not this one.
Assuming you're using es6 class syntax and the most recent version of React (15, at time of writing), you can attach a callback ref like Dan did in his example on the link you shared. From the docs:
When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument. For example, this code uses the ref callback to store a reference to a DOM node:
<h3
className="widget"
onMouseOver={ this.handleHover.bind( this ) }
ref={node => this.node = node}
>
Then you can access the node just like we used to do with our old friends findDOMNode() or getDOMNode():
handleHover() {
const rect = this.node.getBoundingClientRect(); // Your DOM node
this.setState({ rect });
}
In action:
https://jsfiddle.net/ftub8ro6/
Edit:
Because React DND does a bit of magic behind the scenes, we have to use their API to get at the decorated component. They provide getDecoratedComponentInstance() so you can get at the underlying component. Once you use that, you can get the component.node as expected:
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
const rawComponent = component.getDecoratedComponentInstance();
console.log( rawComponent.node.getBoundingClientRect() );
...
Here it is in action:
https://jsfiddle.net/h4w4btz9/2/
Better Solution
A better solution is to just wrap your draggable component with a div, define a ref on that and pass it to the draggable component, i.e.
<div key={key} ref={node => { this.node = node; }}>
<MyComponent
node={this.node}
/>
</div>
and MyComponent is wrapped in DragSource. Now you can just use
hover(props, monitor, component) {
...
props.node && props.node.getBoundingClientRect();
...
}
(props.node && is just added to avoid to call getBoundingClientRect on an undefined object)
Alternative for findDOMNode
If you don't want to add a wrapping div, you could do the following.
The reply of #imjared and the suggested solution here don't work (at least in react-dnd#2.3.0 and react#15.3.1).
The only working alternative for findDOMNode(component).getBoundingClientRect(); which does not use findDOMNode is:
hover(props, monitor, component) {
...
component.decoratedComponentInstance._reactInternalInstance._renderedComponent._hostNode.getBoundingClientRect();
...
}
which is not very beautiful and dangerous because react could change this internal path in future versions!
Other (weaker) Alternative
Use monitor.getDifferenceFromInitialOffset(); which will not give you precise values, but is perhaps good enough in case you have a small dragSource. Then the returned value is pretty predictable with a small error margin depending on the size of your dragSource.
React-DnD's API is super flexible—we can (ab)use this.
For example, React-DnD lets us determine what connectors are passed to the underlying component. Which means we can wrap them, too. :)
For example, let's override the target connector to store the node on the monitor. We will use a Symbol so we do not leak this little hack to the outside world.
const NODE = Symbol('Node')
function targetCollector(connect, monitor) {
const connectDropTarget = connect.dropTarget()
return {
// Consumer does not have to know what we're doing ;)
connectDropTarget: node => {
monitor[NODE] = node
connectDropTarget(node)
}
}
}
Now in your hover method, you can use
const node = monitor[NODE]
const hoverBoundingRect = node.getBoundingClientRect()
This approach piggybacks on React-DnD's flow and shields the outside world by using a Symbol.
Whether you're using this approach or the class-based this.node = node ref approach, you're relying on the underlying React node. I prefer this one because the consumer does not have to remember to manually use a ref other than the ones already required by React-DnD, and the consumer does not have to be a class component either.

Categories

Resources