reactjs this.refs vs document.getElementById - javascript

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.

Related

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.

Is the reason props in React shouldn't be changed/mutated is because React wants elements to be as predictable as possible?

From React documentation.
Conceptually, components are like JavaScript functions. They accept
arbitrary inputs (called “props”) and return React elements describing
what should appear on the screen.
Considering:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
or
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Will give us the ability to do this:
<Welcome name="Luke" />;
<Welcome name="Leia" />;
to use as we wish in the DOM,
Hello, Luke
Hello, Leia
Now when people prescribe props shouldn't be changed, it would make sense the reason is in my thinking would be like the same as changing the values of attributes of an image tag?
HTML:
<img id="Executor" alt="Picture of Executor" src="/somepath/vaders-star-destroyer-executor.jpg"/>
JS:
Meanwhile in a Javascript file a long time ago in a galaxy far, far away...
var imageOfVadersStarDestroyer = document.getElementById('Executor');
imageOfVadersStarDestroyer.src = "/somepath/vaders-star-destroyer-avenger.jpg"
Because if we keeping changing an elements attribute values this can cause confusion and slower renderings?
So is the reason why the prescription is to never change props in React is because is the library is trying to make elements as predictable as possible?
Setting props outside of React is dangerous and should be avoided. Why? The main reason is that it doesn't trigger re-renders. Hence bugs and unexpected behaviour.
Re-rendering
Most of the time, props are data that is store as state in the parent component, which is manipulated by calling setState() (or the second function returned by React.useState()). Once setState() is called, React re-renders and computes what has changed under the hood, with the latest props and state. Manually assigning values to props, therefore won't notify React that the data has changed and something has to be re-rendered.
The good practice
Making props read-only allows React components to be as pure as possible, which is obviously a good practice anyway even when writing plain JS. Data won't be changed unexpectedly and can only be done so by calling setState() (You might have heard of the single source of truth, which is what React is trying to leverage).
Imagine you notice something went wrong in the app and the data shown to the end user is completely different from the source, it would be a pain trying to find out where the data has been manipulated wouldn't it? :)
never change props in React
means that you should never do this.props.name = "userName" because of React's one way data binding, props are read only, to update a component's props, you should pass a function from the parent that will do that ( in the parent ) , or dispatch an action if you're using redux, a change in the props will trigger a re-render
props is a constant in this case. You will always need it in your components.
But there is a cleaner way to write it or even omit it.
Regular way with Function Expression (same as your exemple)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
ES6 Object Destructing - explicit
function Welcome(props) {
const {name} = pros
return <h1>Hello, {name}</h1>;
}
ES6 Object Destructing - inplicit, cleaner way
function Welcome({name}) {
return <h1>Hello, {name}</h1>;
}
And of course, you can use the class way which requires the usage of this.props.yourAttr
However, in the new version 3 of create-react-app, changed class components to functional components. You can see this exact modification on Github here.
You can need to learn more about destructing assignment in the old and good MDN linked here or an in-depth approach both array and object destructuring here.

Attempt to build a generic form component in React without using eval()

I'm currently trying to code a generic form component for my React applications. I know there are lots of great libraries for that out there, but they all seem like an overkill for me at this point.
My main goal here is to have a <BasicForm> component that I can consume, by passing the submit function as props and passing input fields and buttons as children with the validation requirements along for each field.
My <BasicForm> component should handle the form state, onChange, onBlur, onSubmit, etc. And should also handle validation (based on each input type that I add as children).
The motivation was that I was feeling repetitive everytime a needed to create a new form, by having to manage state for inputs, validation, onChange and submit functions over and over.
Here's the code where I'm consuming the <Basic Form>:
BasicForm-Consumer.js
<BasicForm
submitFunction={props.linkEmailAndPassword}
submitAction={'this.props.submitFunction(this.state.passwordOne)'}
>
<PasswordInput
labelText='Password'
name='passwordOne'
placeholder='Enter password...'
min={6}
max={12}
required
/>
<TextInput
labelText='Username'
name='username'
placeholder='Enter username...'
initialValue=''
min={5}
max={10}
required
/>
<button type='submit'>Create Password</button>
</BasicForm>
I'm initially not adding here the source code for the <BasicForm> not to make this question too long, but basically it handles all the input children with React.Children.map and it has validation functions for each kind of input, and also functions to handle change, blur, submit, etc. It also creates a state for each input field.
QUESTION INTRO
<BasicForm
submitFunction={props.linkEmailAndPassword}
submitAction={'this.props.submitFunction(this.state.passwordOne)'}
>
As you can see from the snippet above, in order to send the submitAction from the consumer to <BasicForm>, I have to wrap it in a string, since it references a state that will only exist inside the <BasicForm>. And most importantly, the specific state part that I need to submit is defined outside of the <BasicForm> (in the consumer).
The consumer defines which part or parts of the state should be submitted and it will be different on every case, therefore I can't make it as a "generic" state name that any <BasicForm> would have as a built-in.
So I'm sending as a string and inside <BasicForm> I had to use the most NOT RECOMMENDED eval() as you can see below:
BasicForm.js
onSubmit(event) {
event.preventDefault();
this.validateAllFields();
eval(this.props.submitAction);
}
PROPER QUESTION
How bad is the eval() in this case? Am I exposing my app to "malicious hackers" by doing this? Should I sanitize it in any way, even though it's not a user input? Is there another way around this? I'm really happy by not having to build a form from scratch everytime I need one. Now I just focus on the fields and the validation requirements. Thanks for your time!
EXTRA INFO
P.S.: Here's the code for one of the input components TextInput. The PasswordInput is very much just like this. The only difference is type='password':
TextInput.js
import React from 'react';
import Label from './Common/Label';
const TextInput = (props) => {
return(
<div>
<Label
name={props.name}
labelText={props.labelText}
>
</Label>
<div>
<input
type='text'
id={props.name}
name={props.name}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange}
onBlur={props.onBlur}
>
</input>
</div>
{props.isInvalidMsg && <div><span>{props.isInvalidMsg}</span></div>}
</div>
);
};
export default TextInput;
Eval is almost never welcome because it imposes security risks, performance penalties and indicates that there are design issues.
It depends on its usage if there are security risks. If there's a chance that evaluated expression may contain user input, directly or indirectly, there's security risk. Otherwise only performance and design concerns apply.
In this case this means that BasicForm failed to provide reasonable API to parent components, so it ended up with eval. A proper way to do this is a callback.
Since parent component shouldn't be generally aware of BasicForm implementation details and access its instance, BasicForm this shouldn't be available in a callback. BasicForm props are already available in a callback - they are passed in parent component. Since it's known BasicForm state contains values from its child inputs, it should be available in a callback.
So BasicForm callback may look like:
<BasicForm
onSubmit={state => props.linkEmailAndPassword(state.passwordOne)}}
...
onSubmit(event) {
event.preventDefault();
this.validateAllFields();
this.props.onSubmit(this.state);
}
In case there's a need for onSubmit callback to access any BasicForm data besides its state, this means that this data should be passed as callback argument, too.

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

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"

Stateless function components cannot be given refs

I try to access some refs in my component. But I have this error in the console.
withRouter.js:44 Warning: Stateless function components cannot be given refs (See ref "pseudo" in FormInputText created by RegisterForm). Attempts to access this ref will fail.
Here is my component:
class RegisterForm extends React.Component {
render() {
return (
<form action="">
<FormInputText ref="pseudo" type="text" defaultValue="pseudo"/>
<input type="button" onClick={()=>console.log(this.refs);} value="REGISTER"/>
</form>
);
}
}
Plus when I click on the button I got Object {pseudo: null}in the console. I would expect an object instead null.
I am not sure to understand why this is not working. Note that my react tree uses mobx-react.
Refs do not work with stateless components. It is explained in the docs
Because stateless functions don't have a backing instance, you can't attach a ref to a stateless function component.
Stateless components at the moment of writing actually have instances (they are wrapped into classes internally) but you can not access them because React team is going to make optimizations in the future. See https://github.com/facebook/react/issues/4936#issuecomment-179909980
You could also try using recompose it has a function called toClass.
Takes a function component and wraps it in a class. This can be used as a fallback for libraries that need to add a ref to a component, like Relay.
If the base component is already a class, it returns the given component.

Categories