React ref attribute using callback attaches a DOM node to my class - javascript

I am currently using strings for ref= in React 15.3.2, and such is deprecated.
So if I use a callback per the docs:
ref={(input) => this.textInput = input}
Then this will attach a DOM Element to my class when the component mounts.
Yay! A DOM element. So now I do not have to do:
ReactDOM.findDOMNode(this.refs.input).value=''; // uncontrolled component
I thought the whole idea of react is to not touch the DOM...
Lets assume I have a TextInput component that is complex, and has an InputError component along with <input type="text" -- this TextInput component does a bit of validation based on props, and it has a state that helps things like when to show error.
Back up in the form, I want to clear the form, so TextInput has a .clear() which resets its state. (Am I doing it wrong already?)
The thing is, I cannot access any child components React Objects unless I use strings as ref=, so I cannot call clear().
What gives?
Am I supposed to route "all communication" through props? What good is this.refs once all refs are callbacks? Can I get my react objects through .children or something? What is the convention on this issue?
Edit:
Obviously I am learning React, I guess the basic question is, is it bad (anti-react) to EVER call methods on a child component?
The components in question can be found here:
RegisterForm.jsx
TextInput.jsx
InputError.jsx
The requirement I find difficult/strange making work via props is TextInput: "onblur then if error then show error, mark as errored until changing passes validate"

Related

Prevent unnecessary re-renders of components when using useContext with React

I've seen a few questions related to this topic, but none that tackle the issue head-on in a pure way. useContext is a great tool for minimizing prop-drilling and centralizing key data needed across your app; however, it comes at a cost that I'm trying to minimize.
The closest issue to the one I'm describing here was asked two years ago here. The question didn't gain a lot of traction and the only answer basically says call the context in a parent container and pass values down through props (defeats the purpose of context) or use Redux. Maybe that's the only way, but I wanted to bring the question back to the collective to see if there is a better answer that's not dependent on external libraries.
I've set up a code sandbox here to illustrate the issue. Re-renders are console logged out to make seeing them easier.
In short, when a state changes in context, every app accessing data from that context re-renders, even if it's not utilizing the state data that changed (because it's all ultimately passed through the value object). React.Memo does not work on values accessed from context the same way it does for properties.
For example, in the code sandbox linked above, in App.js if you comment out <AppContainerWithContext /> and load <AppContainer />, there are two states managed in the container, one called titleText and the other called paragraphText. titleText is passed as a prop to component called TitleText and paragraphText is passed to a component called ParagraphText. Both components are wrapped in React.memo(). There are two buttons called in the AppContainer and each has a function that changes the text back and forth based on the value of separate boolean states.
Here is the function that toggles the titleText, the one for paragraph text is the same logic:
const changeTitleHandler = useCallback(() => {
const title = listTitleToggle ? "Title B" : "Title A";
setListTitleToggle((pV) => !pV)
setTitleText(title);
}, [listTitleToggle]);
Since the titleText component and paragraphText components are wrapped with React.useMemo, they only re-render when the corresponding value passed to them changes. Perfect.
Now in App.js if you comment out the <AppContainer /> component and enable the <AppContainerWithContext /> component, the rendered output and result of button clicks is identical; however, the states that change and are rendered on the screen are now managed by AppContext (called contextTitleText and contextParagraphText and passed to the TitleText component and ParagraphText component via useContext.
Now, if you click on the button to toggle the title, the ParagraphText component re-renders too, even though it doesn't use the contextTitleText state. My understanding of why this happens is because the value object changes when the contextTitleText is updated, causing any component accessing that value object through useContext to re-render.
My question is this:
Is there a way to utilize useContext without causing re-renders on all components accessing the context. In the example above, can we utilize useContext to manage the contextTitleText and the contextParagraphText but only re-render the components where the state from context being accessed changes?

Why use refs in react? What is the use cases for it?

This question exists but it didn't give a lot of data or real world explanation: What are Refs in React or React-Native and what is the importance of using them
Let's say i want to integrate to 3rd party library how ref is going to help me?
Some 3rd party libraries expose methods to interact with their components.
For example, in react-native-elements npm, they have shake method for Input component. You can use this method to shake Input element when user input is invalid.
Common use case is as follows:
import React from 'react';
import { Input, Button } from 'react-native-elements';
const [value, setValue] = useState('');
const input = React.createRef();
return (
<View>
<Input
ref={input}
onTextChange={(text) => setValue(text)}
/>
<Button
title={'Submit'}
onPress={() => {
if (!isValid(value)) {
input.current.shake();
}
}}
/>
</View>
);
This is react native example, but the similar goes to react projects. I hope you get the picture. Animations like shake cannot be easily handled with state, so it's better to use useRef to call component methods directly.
Let's say i want to integrate to 3rd party library how ref is going to help me?
Refs let you access the DOM directly, thus you can use vanilla js libraries using refs, for example you could use jQuery like $(ref). This simplifies and makes getting DOM nodes less error prone than using other techniques such as adding classes/ids to every element and then using selectors since these methods do not stop you from accessing nodes not created by you.
Long story short, Refs let you treat react elements as though they were vanilla js
React useRef help us to accessing dom elements before its rendering.
You can go through it
https://reactjs.org/docs/refs-and-the-dom.html
Whenever you want to use the properties of child from a parent, we refer it with a ref id, this is to ensure we are executing on the right child component. The properties can be either states, props of functions defined in the child component.

reactjs this.refs vs document.getElementById

If I have just a basic forms, should I still this.refs or just go with document.getElementById?
By basic I mean something like:
export default class ForgetPasswordComponent extends React.Component {
constructor(props) {
super(props);
this.handleSendEmail = this.handleSendEmail.bind(this);
}
handleSendEmail(e) {
e.preventDefault();
// this.refs.email.value
// document.getElementById('email').value
}
render() {
<form onSubmit={this.handleSendEmail}>
<input id="email" ref="email" type="text" />
<input type="submit" />
</form>
}
}
Is there a downside into using one over the other?
In general, refs is better than document.getElementById, because it is more in line with the rest of your react code.
In react, every component class can have multiple component instances.
And as #Isuru points out in comments: using id is dangerous, because react does not prevent you to have multiple forms on 1 page, and then your DOM contains multiple inputs with same ID. And that is not allowed.
Another advantage to using refs, is that by design, you can only access the refs in the context where you define it. This forces you to use props and state (and possibly stores) if you need to access info outside of this context.
And this an advantage, because there is less/ no chance of you breaking your unidirectional data flow, which would make your code less manageable.
NB: In almost all cases, refs can be avoided altogether. It is a design principle for Netflix to use no refs, ever, as explained by Steve McGuire (Senior User Interface Engineer at Netflix) in this video from reactjs conf 2016 (9:58m into the video).
In your case, this would mean putting the email-input value in state of the form, add on onChange handler, and use the state value in the submit event.

this.refs.something returns "undefined"

I have an element with a ref that is defined and ends up getting rendered into the page :
<div ref="russian" ...>
...
</div>
I want to access the DOM element properties like offset... or something. However, I keep getting undefined and I haven't the faintest idea why. After some searching it's clear that refs are only applicable to one file but I'm not using this anywhere besides this one page. I'm saying this to log it:
console.log('REFS', this.refs.russian);
What could be causing this?
Check that you are not accessing ref before the child component has been mounted. E.g. it doesn't work in componentWillMount. A different pattern which auto invokes ref related callback after the element has been mounted is this-
<div ref={(elem)=>(console.log(elem))}/>
You can use this notation to get mounted elements in deep nesting as well -
<div ref={this.props.onMounted}/>
The correct place to work with refs is inside specific React lifecycle methods e.g. ComponentDidMount, ComponentDidUpdate
You cannot reference refs from the render() method. Read more about the cautions of working with refs here.
If you move your console.log('REFS', this.refs.russian); call to ComponentDidMount or ComponentDidUpdate lifecycle methods (assuming you are on React >= 14) you should not get undefined as a result.
UPDATE: also refs will not work on stateless components per the caution link above
Update since React version 16.4
In your constructor method define your ref like this
constructor(props) {
super(props);
this.russian = React.createRef();
}
In your render where you are using ref do this.
<input
name="russian"
ref={this.russian} // Proper way to assign ref in react ver 16.4
/>
For e.g if you want to have focus when component mounts do this
componentDidMount() {
console.log(this.russian);
this.russian.current.focus();
}
Reference Refs Documentation React
I was having a similar issue in my form validation methods, trying to assign this.ref.current.reportValidity()
Writing the method I was doing this in as validate = () => {} instead of validate() {} helped me out, but I'm not totally sure why exactly, just something I remembered from habits I had in my past work experience that gave me this. Hope it helps and could someone kindly clarify this answer with why this might work exactly.
If you are exporting class withStyle, please remove and export default nomally.
Instead of putting your Console.log inside the function example(){...} you should put it inside:
example=()=>{....}

Is my React component being recreated instead of updating?

I am trying to combine Angular and React.js. I have an work example project here I have seen a couple of ways to bring the Angular and React.js together. One of the methods I have seen is to create a directive and create the React component in the link function. For example in the first part of the project to generate the React version(in red) I am using
.directive('reactElementRepeater', function() {
return {
link: function(scope, element) {
var update_react = function(oldVal, newVal){ //Called every time one of the two values change
React.renderComponent(Demo_Element({
numberOfElements: scope.myModel.numberOfElem,
numberInElements: scope.myModel.numberInElem
}), element[0]);
}
scope.$watch('myModel.numberOfElem.length', update_react);
scope.$watch('myModel.numberInElem', update_react);
}
}
});
What I want and what should happen in a React enabled application is for something in the model to be updated, then that update is sent through React and it will alter the DOM as little as possible to reflect that change. It looks like that instead of updating a bit of the DOM this will Create a new React component each time with renderComponent.
React.renderComponent() instantiates the root component, starts the
framework, and injects the markup into a raw DOM element, provided as
the second argument.
Is it actually recreating the elements each time? If that is the case is there a way to alter this so that doesn't happen?
Just to be clear I know about ngReact, I just want to know other ways to speed up Angular with React.
Yes this is fine, it's not mounting the component multiple times.
When you call React.renderComponent() the second argument is the element which react should render the component to. So react notices if you are rendering the same component to a dom element that already contains a mounted instance of the component, and does not re-mount the entire component, it just updates the properties of it instead.
You can see this in action if you make a component with componentDidMount function defined. You'll notice that componentDidMount will only execute the first time renderComponent gets called. And afterwards, subsequent calls to renderComponent on the same target dom element will not call it because the component is already mounted. Likewise getDefaultState and getDefaultProps also only get called on the first renderComponent call.
If you're asking will the render function of the component be called every time the answer is yes. But this is how react works, you want the render function to get called because props might have changed. You can block it from being called by using shouldComponentUpdate (http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) and returning false. However react developers recommend you don't use this to block render calls unless you have specific performance problems - most of the time it should be fine to just let the render call execute as it wont cause any slow dom updates unless things have actually changed.

Categories