How to build uncontrolled component with react hooks? - reactjs

How can I translate this uncontrolled component to react hooks (and avoid multi render of the input field):
class MyForm extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
render() {
const {onInputChanged} = this.props;
return (
<form>
<input type="text" ref={this.input} onChange={onInputChanged}/>
</form>
);
}
}

If you want to convert your component to a functional component and use hooks you can do this way:
import React, { useRef,useEffect } from 'react';
useEffect(() => {
},['the props you write here will trigger the re-render when they change'])
const MyForm = (props) => {
const input = useRef(null);
return (
<form>
<input type="text" ref={input} onChange={props.onInputChanged} />
</form>
);
}
export default MyForm;

Take a look at the useRef hook: https://reactjs.org/docs/hooks-reference.html#useref

import React, { useRef } from 'react';
export const MyForm = (props) => {
const input = useRef(null);
return (
<form>
<input type="text" ref={input} onChange={props.onInputChanged} />
</form>
);
}

Related

Why does componentDidMount fires in a HOC where as componentDidUpdate does not fire?

I am following a reactjs tutorial on higher order components(HOC). I want a HOC to log props when props change.
import React, { useState } from 'react';
function logProps(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log("Component was mounted");
}
componentDidUpdate(prevProps) {
console.log("Current props: ", this.props);
console.log("Previous props: ", prevProps);
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />
}
}
}
class CustomDivElement extends React.Component{
render(){
return <div>{this.props.text}</div>
}
}
function App(props) {
const [text, setText] = useState("");
const EnhancedComponent = logProps(CustomDivElement);
return (
<div tabIndex="0" className="App ui container">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)} />
<EnhancedComponent text={text} />
</div>
)
}
export default App
At first I thought it's because I am using a HOC. So I introduced another lifecycle method which is componentDidMount and it's firing. The componentDidUpdate is not firing, why is that?
Because you unmount the component on every render, the component doesn't get to componentDidUpdate lifecycle.
function App(props) {
// Remount on every render
const EnhancedComponent = logProps(CustomDivElement);
...
}
Instead, when using HOC, or any other Component, you want to mount it once:
// export default logProps(CustomDivElement)
const EnhancedComponent = logProps(CustomDivElement);
function App(props) {
const [text, setText] = useState("");
return (
<div tabIndex="0" className="App ui container">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)} />
<EnhancedComponent text={text} />
</div>
)
}

Can't get event.target.value in React

I'm trying to get a reaction from onInputChange function when I use it with onChange on input, but I didn't get why it's not console.log the value. Any help?
App.js
import React, { Component } from 'react';
import Output from './Components/Output';
import NumberInput from './Components/NumberInput';
import './App.css';
class App extends Component {
constructor() {
super()
this.state = {
decimal: 0,
inputString: ''
}
}
onInputChange = (event) => {
console.log(event.target.value)
}
render() {
const { decimal, inputString } = this.state;
return (
<fragment>
<h1>Binary to Decimal convertor</h1>
<NumberInput InputChange={this.onInputChange} />
<button>Submit</button>
<Output string={inputString}/>
</fragment>
);
}
}
export default App;
NumberInput component:
import React from 'react';
const NumberInput = ({ inputChange }) => {
return (
<fragment>
<input type='search' onChange={inputChange}></input>
</fragment>
)
}
export default NumberInput;
InputChange should be camelcase
change
<NumberInput InputChange={this.onInputChange} />
to
<NumberInput inputChange={this.onInputChange} />
you may also need to bind
<NumberInput inputChange={(e) => this.onInputChange(e)} />
Need some changes
Change your InputChange={this.onInputChange}
to inputChange={this.onInputChange} in App component.
because you are accessing inputChange in your NumberInput component.
And also change your fragment tag as well because you used fragment instead of Fragment tag.
Either use <Fragment>, <React.Fragment> or <> in NumberInput, App component like-
<Fragment>
<input type='search' onChange={inputChange}></input>
</Fragment>

Passing Event from Parent to Child-Child

I would like to know how to accomplish this.
Passing an event from Parent to Child/Child, by this example: I have Apps.js, then FormDynamic.js and two Inputs. I want to pass handleChange from the Apps.js to FormDynamic.js and then to InputTextField.js and get the value from the App.js on the submitForm function.
//Apps.js
import React, {Component} from 'react';
import FormDynamic from './components/FormDynamic'
class App extends Component {
constructor(props)
{
super(props);
this.state={
fields: [
{id:"101", name:"101", placeholder:"Enter Value 1",input_type:"text",required:true},
{id:"102", name:"102", placeholder:"Enter Value 2",input_type:"number",required:true}
]
}
this._handleChange = this._handleChange.bind(this);
}
_handleChange = event =>{
this.setState({
[event.currentTarget.id]: event.currentTarget.value
});
};
submitForm = event =>{
const {fields, ...inputFields} = this.state;
console.log(fields);
console.log(inputFields);
event.preventDefault();
};
render(){
return (
<div className="App">
<FormDynamic fields={this.state.fields} handleChange={this._handleChange} />
<button onClick={this.submitForm}>Enviar Datos</button>
</div>
);
}
}
export default App;
//FormDynamic.js
import React, {Component} from 'react';
import InputTextField from './InputTextField'
import InputNumberField from './InputNumberField'
class FormDynamic extends Component
{
constructor(props)
{
super(props);
}
render()
{
return(
<div>
{this.props.fields.map(form => {
if (form.input_type ==="text")
{
return (
<InputTextField
id={form.id}
name={form.name}
placeholder={form.placeholder}
required={form.required}
key={form.id}
onChange = {this.props.handleChange}
/>
);
}
if (form.input_type ==="number")
{
return (
<InputNumberField
id={form.id}
name={form.name}
placeholder={form.placeholder}
required={form.required}
key={form.id}
onChange = {this.props.handleChange}
/>
);
}
return null;
})}
</div>
)
};
}
export default FormDynamic;
//InputTextField.js
import React from 'react';
const InputTextField = ({id,name,placeholder,required,_handleChange}) =>
(
<div>
<input type="text"
id={id}
name={name}
required={required}
placeholder={placeholder}
onChange={_handleChange}
/>
</div>
);
export default InputTextField;
From FormDynamic.js your passing props onChange to InputTextField.js but your reading wrong props _handleChange instead read onChange props, try the below code it will work.
//InputTextField.js
import React from 'react'
const InputTextField = ({id, name, placeholder, required, onChange}) => (
<div>
<input type="text"
id={id}
name={name}
required={required}
placeholder={placeholder}
onChange={onChange}
/>
</div>
)
export default InputTextField

React render props.children fails when run children events (children are components)

I'm creating a ReactJS package. I've created a component called Form which contains a normal form. When calling it on the App.js I add components on it which called TextField. So, the code on App.js is:
import React, { Component } from 'react'
import { Form, TextField } from 'form2'
import './App.css'
export default class App extends Component {
render () {
return (
<div>
<Form>
<TextField placeholder="Type your name..." />
<TextField placeholder="Type your email..." />
</Form>
</div>
)
}
}
And the code of the Form component is:
import React, { Component } from 'react'
class Form extends Component {
constructor(props) {
super(props)
this.state = {
values: {}
}
}
renderChildren = () => {
return this.props.children;
};
render() {
return <form>
{ this.renderChildren() }
</form>
}
}
export default Form
And this is mt TextField component:
import React, { Component } from 'react'
class TextField extends Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
onChange = ({ target }) => {
this.setState({ value: target.value });
};
render() {
return <input onChange={this.onChange} {...this.props}>
{this.state.value}
</input>
}
}
export default TextField;
The problem is happening when I type something on the input, the inputs disappeared and got this error message on the console:
The above error occurred in the <input> component
and this one
Uncaught Invariant Violation: input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.
Change:
render() {
return <input onChange={this.onChange} {...this.props}>
{this.state.value}
</input>
}
To:
render() {
return <input defaultValue={this.state.value} onChange={this.onChange} {...this.props} />
}
Or (depending on the behavior your want):
render() {
return <input value={this.state.value} onChange={this.onChange} {...this.props} />
}
The existing code is trying to set the child of input to {this.state.value}.

React equivalent to ng-model

New to React, I have a TextField that I want to "bind" a value to it, I want it to change the value if the user enters value into the TextField and update the TextField if the value change through some API call.
Is there a way to do it ?
You can do this using state and onChange. Simple example included below:
<TextField
onChange={(name) => this.setState({name})}
value={this.state.name}
/>
A guide for updating TextInput based on a variable is located in the docs.
The way to do this in React is with state. Here's an example in JSX:
import React from 'react';
export default class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'default',
text2: 'default'
}
}
onChange(e) {
var obj[e.target.name] = e.target.value
this.setState(obj);
}
render() {
return (
<div>
<input type="text" name="text" value={this.state.text} onChange={this.onChange} />
<input type="text" name="text2" value={this.state.text2} onChange={this.onChange} />
</div>
);
}
}
The same thing with React hook
import React,{useState} from 'react';
export default MyForm = () => {
const [text, setText] = useState('default')
const [text2, setText2] = useState('default')
return (
<div>
<input type="text" name="text" value={text} onChange={(e)=> setText(e.target.value)} />
<input type="text" name="text2" value={text2} onChange={(e)=> setText2(e.target.value)} />
</div>
)
}
Two ways data binding in angular work out of the box by using ngModal directive
NgModal: Creates a FormControl instance from a domain model and binds it to a form control element
import {Component} from '#angular/core';
#Component({
selector: 'example-app',
template: `
<input [(ngModel)]="name" #ctrl="ngModel" required>
<p>Value: {{ name }}</p>
<p>Valid: {{ ctrl.valid }}</p>
<button (click)="setValue()">Set value</button>
`,
})
export class SimpleNgModelComp {
name: string = '';
setValue() { this.name = 'Nancy'; }
}
In ReactJS, we don't have such a built-in option to handle two ways data binding, you need to use the state and add an onChange event to the input.
React Controlled Input allow us to implement data binding in React: we bind a value to a state variable and an onChange event to change the state as the input value changes.
Refer to the below snippet:
class App extends React.Component {
constructor() {
super();
this.state = {name : ''}
}
handleChange = (e) =>{
this.setState({name: e.target.value});
}
render() {
return (
<div>
<input type="text" value={this.state.name} onChange={this.handleChange}/>
<div>{this.state.name}</div>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
This is pretty straightforward using useState React Hook and TextInput
import { useState } from "react";
import { TextInput, Text, View } from "react-native";
const App = () => {
const [text, setText] = useState("hello");
return (
<View>
<TextInput value={text} onChangeText={setText} />
<Text>Text value: {text}</Text>
</View>
);
};
export default App;
See it working here

Resources