Why behavior is different? - javascript

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.

Related

How to convert react-to-print libs into next js [duplicate]

Let's say I have a component with a scrollable subcomponent, and I want to expose the ability to scroll:
const MyComponent = (props) => {
return <ScrollView ... />
}
I want to be able to do
<MyComponent ref={myRef} />
...
myRef.scrollTo({x: 0});
So I need a way to forward the ref to the <ScrollView>. Let's try putting the ref on the props:
const MyComponent = (props) => {
return <ScrollView ref={props.scrollRef} ... />
}
...
<MyComponent scrollRef={myRef} />
...
myRef.scrollTo({x: 0});
I just tried that with React Native on iOS, and it indeed works. I see several advantages over React.forwardRef:
Simpler, because I don't need to use another React API.
Works also if there is more than one child who needs ref forwarding.
Seems to me that this approach is
What's the advantage of React.forwardRef? Why was it added in React 16.3?
Note that there is no difference between using another named prop like innerRef FOR FORWARDING, it works the same.
Refactoring class components
Since React moved toward function components (hooks) you might want to refactor the class component code to a function component without breaking the API.
// Refactor class component API to function component using forwardRef
<Component ref={myRef} />
React.forwardRef will be your only option (further explained in details).
Clean API
As a library author you may want a predictable API for ref forwarding.
For example, if you implemented a Component and someone wants to attach a ref to it, he has two options depending on your API:
<Component innerRef={myRef} />
The developer needs to be aware there is a custom prop for forwarding
To which element the innerRef attached? We can't know, should be mentioned in the API or we console.log(myRef.current)
<Component ref={myRef} />
Default behavior similar to ref prop used on HTML elements, commonly attached to the inner wrapper component.
Notice that React.forwardRef can be used for function component and HOC (for class component see alternative below).
Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.
For function components, forwardRef sometimes comes with useImperativeHandle combo (in class component you just call the class methods on ref instance: ref.current.myAttr().
// Same usage
<Component ref={myRef} />
const Component = React.forwardRef((props, ref) => {
// you can forward ref <div ref={ref} />
// you can add custom attributes to ref instance with `useImperativeHandle`
// like having ref.myAttribute() in addition to ones attached to other component.
});
Important behavior of ref prop without forwardRef.
For the class component, this code alone will attach the ref to CLASS INSTANCE which is not useful by itself and need another ref for forwarding:
// usage, passing a ref instance myRef to class Component
<Component ref={myRef} />
Full example, check the logs:
// We want to forward ref to inner div
class ClassComponent extends React.Component {
innerRef = React.createRef();
render() {
// Notice that you can't just `this.props.ref.current = node`
// You don't have `ref` prop, it always `undefined`.
return <div ref={this.innerRef}>Hello</div>;
}
}
const Component = () => {
const ref = React.useRef();
useEffect(() => {
// The ref attached to class instance
console.log(ref.current);
// Access inner div through another ref
console.log(ref.current.innerRef);
}, []);
return <ClassComponent ref={ref} />;
};
In function components, it won't even work because functions don't have instances.
By default, you may not use the ref attribute on function components because they don’t have instances. [1]
forwardRef.
Refs and the DOM.
Why we need ref forwarding?

Can somebody explain what makes this code?

I've hot React component which returns input tag. Can you please explain what is going on at the eighth line ref={element => element && (element.onChange = onChange)}? I
import React from 'react';
export default function MyInput({
onChange,
...rest
}) {
return (
<input
{...rest}
ref={element => element && (element.onChange = onChange)}
/>
);
}
React's ref is used to access the DOM directly, and in general is recommended to use as less as possible. The point of functional refs, and keep in mind that they're deprecated, is to assign the element into a class component's variable. e.g.:
Class MyComponent extends Component {
constructor(props) {
super(props);
this.inputRef = null;
}
...stuff
render() {
...stuff
<input ref={element => this.inputRef = element} />
}
}
Then, you could do something like:
this.inputRef.current.style.color = 'blue';
In your case, there is no need for this. If you want to assign the onChange you get from props, just do this:
<input {...stuff} onChange={onChange} />
Read more about React refs here.
As for element && element.onChange, it's designed to make sure that element exists before accessing it's onChange property. Another way to do it, using optional chaining (only avaliable in react-scripts v3.3 and above), is this:
element?.onChange
Refs are used to access DOM elements
The value of ref differs depending on the type of node:
When the ref attribute is used on an HTML element, the ref created
in the constructor with React.createRef() receives the underlying
DOM element as its current property.
When the ref attribute is used on a custom class component, the ref
object receives the mounted instance of the component as its
current.
They are used in cases where we want to change the value of a child component, without making use of props and all. But in your case, i think you have no need to use ref because you simply wants to assign onChange that you received from props.

Updating Parent Component state from multiple child components' componentDidMount() synchronously

Ok so this question is a bit tricky. I have been thinking about whether this is even correct concept wise, considering React is supposed to be a one-way flow of data, from parent to children, and not viceversa. But I would like to post the question anyway so I get different opinions and even possibly a way to get this to work.
In my app, I have a pretty large component that accepts forms as its children, and does some nifty React magic to pass its methods to the children so when the children elements are changed, they trigger the parent components methods that store the data in state and handles the form submissions. It works very nicely, however it is not so good at catching "defaultValues".
In a nutshell, I'm trying to trigger my parent method on the chilren's componentidMount() method, and it works, however, if there's more than one child trying to do this, the method gets called twice but it only uses the second child's dataset.
I have created a simplified version of my issue in the following code:
import React from 'react'
export class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
data : {name:'james'}
}
this.updateData = this.updateData.bind(this)
}
updateData(key,data){
console.log('updating data')
this.setState({
data : {...this.state.data,[key]:data}
})
}
render(){
console.log(this.state)
return (
<div>
<Child1 updateData={this.updateData}/>
<Child2 updateData={this.updateData}/>
</div>
)
}
}
class Child1 extends React.Component {
componentDidMount(){
this.props.updateData('child1','myData')
}
render(){
return (
<div>
I am Child 1
</div>
)
}
}
class Child2 extends React.Component {
componentDidMount(){
this.props.updateData('child2','myData2')
}
render(){
return (
<div>
I am Child 2
</div>
)
}
}
This code will render 'updating data' twice on the console, but it will only update the state with the data sent in child2. Again, I can see how this may not be the best approach considering that im setting the state of a parent from its children, but it would be a good solution for setting default values on a parent component that gets reused a lot with different children.
Im all ears stack overflow
I think the problem is that setState does both updates at the same time (batches them) meaning the same initial state is used when merging both partial states. You need to use updater function as shown by kind user:
this.setState((prevState) => ({ data: { ...prevState.data, [key]: data } }));

React. Cannot get the component element in the DOM

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}

React.js - use DOM elements from different levels of component hierarchy

We need to perform some actions related to DOM elements in different parts of the component hierarchy, on window.onresize and window.onscroll:
1) Moving elements when resizing to mobile resolution
2) Changing classes and styles when scrolling.
The problem is that the elements are in different levels of the React component hierarchy, i.e. nested in different components. Thus if I understand correctly, I can't use refs to access the DOM nodes.
I have:
{someCondition ? <FirstComponent/>: <SecondComponent/>}
Whereas someCondition can change either due to user UI actions, or it could be true from the beginning of the page load.
I've tried using componentDidMount and componentDidUpdate on my FirstComponent, but found that componentDidMount only fires if someCondition is true from the beginning, and componentDidUpdate indeed fires when someCondition changes, but then the required DOM elements (contained in the component) are not ready at that point, and document.getElementById fails.
Adding window.requestAnimationFrame did not help.
I imagine there should be a solid way to do this in React?
Maybe React.findDomNode?
Thank you.
What you are describing is the antithesis of React. It is an approach I would expect when using an imperative jQuery/Dojo based framework.
With React, you are developing functional components that know how to convert state into rendered HTML and you need to approach the problem differently.
For your problem, your window.onresize and window.onscroll callbacks should not try to manipulate the React DOM elements. Instead it should just update state and let React re-render. Your React components should receive this state and render accordingly.
Here's a simple example where the main app listens to these events and updates its state, which triggers a re-render. The app passes the state as props to the children. The children use the props to conditionally change their css class names. They could easily render different html or inline styles as well.
const Component2 = ({someCondition2}) => {
const className = someCondition2 ? "foo" : "bar";
return <div className={className}>Hello</div>;
};
const Component1 = ({someCondition1, someCondition2}) => {
const className = someCondition1 ? "A" : "B";
return (
<div className={className}>
<Component2 someCondition2={someCondition2} />
</div>
);
};
class App extends React.Component {
state = {
someCondition: false,
someCondition2: true,
};
componentDidMount() {
window.onresize = ev => {
const someCondition = ev.xxx;
// re-render with the new state
this.setState({ someCondition });
};
window.onscroll = ev => {
const someCondition2 = ev.xxx;
this.setState({ someCondition2 });
};
}
render() {
const {someCondition, someCondition2} = this.state;
return (
<Component1
someCondition1={someCondition1}
someCondition2={someCondition2} />
);
}
}
React.render(<App />, document.getElementById("container"));

Categories