I created a component and wanted to wrap text input and add some functionality to it.
<Input value={this.state.name} />
In child component there is remove button beside text input in order to clear text when it is clicked.
const Input = (props) => {
let textInput = null;
const removeText = (e) =>{
e.preventDefault();
textInput.value = '';
textInput.focus();
}
return(<div>
<input ref={(input) => { textInput = input; }} {...props} />
<button
onClick = {removeText}
></button>
</div>)
}
As the Input component is function, I used ref in order to access input and manipulate it. The problem is the parent state is not updated when it is changed by functions in child component. Consider that I don't want to use props and pass the function to update parent state. By the way, I don't know this approach whether is correct or not.
Define removeText function in a component where you are calling Input component. Also avoid using refs for input field you no need ref for sure instead you can have event handler function which will set the value in state
removeText = (e) =>{
e.preventDefault();
this.setState({name: ''});
}
handleInput = e => {
this.setState({name: e.target.value});
}
And pass down functions to Input component as a prop like
<Input value={this.state.name} removeText={this.removeText} handleInput={this.handleInput} />
Now, change the Input component to something like below
const Input = (props) => {
return(<div>
<input value={props.value} onChange={e => props.handleInput(e)}/>
<button
onClick = {() => this.removeText()}
></button>
</div>)
}
This way it updates the parent state value. This is so called callbacks in react
Related
I am new to react and I am trying to make an application that gets the output from a form and displays it in a div. So far, I have only managed to get the result from the form with a simple useState() approach.
To display it, I have tried creating a variable "isSubmitted" to keep track whether or not the variable was submitted, in order to display it only when the user stops typing!
const Example0 = () => {
var isSubmitted;
const [email, setEmail] = useState("");
const handleEmail = (event) => {
setEmail(event.target.value);
isSubmitted = false;
console.log(isSubmitted);
};
const handleSubmit = (event) => {
event.preventDefault();
isSubmitted = true;
console.log(isSubmitted);
};
return (
<Fragment>
<h1>Form example</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={email} onChange={handleEmail} />
</div>
<button type="submit">Submit</button>
</form>
<div>{isSubmitted ? email : "no user"}</div>
</Fragment>
);
};
This part, does not work. The email variable changes but the display doesn't. Does it have something to do with how React works? I come from a HTML background.
<div>{isSubmitted ? email : "no user"}</div>
Help is much appreciated.
Your page needs to be rendered, so you can see the data already in your page.
In reactJS, there are states,
The state is a built-in React object that is used to contain data or
information about the component. A component's state can change over
time; whenever it changes, the component re-renders. source
So you need to use a state to make your component renders again and see the data.
simply create a new state like this:
const [isSubmitted, setIsSubmitted] = React.useState(false);
then change the states when you need, In your example you have to use it like this:
const handleEmail = (event) => {
setEmail(event.target.value);
setIsSubmitted(false);
console.log(isSubmitted);
};
const handleSubmit = (event) => {
event.preventDefault();
setIsSubmitted(true);
console.log(isSubmitted);
};
You should be using a state to control the boolean for if the user has submitted their email or not.
Why can't you use a normal variable?
Every time you're updating a state variable, the component will re-render, and each time it re-renders, you're setting your isSubmitted flag to an undefined. Additionally, even if it wasn't undefined, it wouldn't update your component to show the email because non-state variables won't trigger a re-render, so updating isSubmitted to true won't trigger your ternary statement you have, and re-rendering it will just take it back to undefined, which leaves both scenarios without a render of your div tag content.
Solution
If you want something to conditionally render, you should use useState, its the whole point of react, to "react" accordingly and update your DOM. It's one of the biggest issues I see new learners grasp when first touching React.
Here's my approach to it, which won't update the end tag to whatever the value is after you submit it (I assume you'd want this implemented):
const [email,setEmail] = useState("");
const [isSubmitted, setIsSubmitted] = useState(false);
/**
* handles the Email input, does not update if the form has been submitted or if the string is empty.
* #param {*} e
*/
const handleEmail = (e) => {
if(e.target.value.length > 0 && isSubmitted === false) {
setEmail(e.target.value);
}
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitted(true);
};
return (
<>
<h1>Form example</h1>
<form onSubmit={(e) => handleSubmit(e)}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" onChange={handleEmail} />
</div>
<button type="submit">Submit</button>
</form>
{isSubmitted ? <p>Thank you for submitting your email: {email}</p> : null}
</>
I'm using useState in the following way to toggle editing an input:
const [editing, setEditing] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
// returning:
<input
ref={inputRef}
disabled={!editing}
type="text"
name="name"
id="name"
defaultValue={organization?.name}
/>
<button
onClick={() => {
setEditing(!editing);
// inputRef.current?.focus();
}}
type="button"
>
<span>{editing ? "save" : "edit"}</span>
</button>
When the user clicks on "edit", editing becomes true which toggles the <input> to be no longer disabled and the input should be focused but it is not.
In order to get this working, I ended up using useEffect the following way:
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing, inputRef]);
This works, but it just seems a bit much. Is there a way to "await" setState or something to ensure the new state took effect, so that I can then do this focus()?
The correct way of doing this for functional components is as you've done it, with useEffect.
React does not support state updates through the useState Hook, as Component#setState does.
If you wish to use a class component, however, then you can add a callback to the second argument of setState:
import React, { Component } from "react";
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
};
render() {
return (
<>
<input
ref={this.inputRef}
disabled={!this.state?.editing}
type="text"
name="name"
id="name"
/>
<button
onClick={() => {
this.setState({ editing: !this.state?.editing }, () => this.inputRef.current?.focus());
// inputRef.current?.focus();
}}
type="button"
>
<span>{this.state?.editing ? "save" : "edit"}</span>
</button>
</>
);
};
};
Which executes after the state has been set.
By attempting to use a callback argument in your set (state) function, React would throw the following error:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
I am missing how to bring the input value out of the child component.
I have an Input component that I want to re-use, there I can see what happens onChange, and I see every change on the input field.
Then on the parent component, Form, where I use <Input />, I have the rest of the form with the Submit button. At this level, I want to handle onSubmit, but I cannot see/console the value of the input here. I can only see it from the child.
Any ideas about what I am doing wrong?
Input.js - here I can see the input value onChange
function Input(props) {
const { label, name, value } = props;
const handleChange = (event) => {
const updateForm = {...Form};
console.log("change:", updateForm)
updateForm[label] = event.target.value;
}
return (
<label>
{label}
<input name={name} value={value} onChange={handleChange}></input>
</label>
)
}
export { Input }
Forms.js - here I cannot get access to the input value and submit/handle it
function Form(props) {
const handleSubmit = (event) => {
event.preventDefault();
console.log(Input.value);
console.log(props.label.value)
alert(`form is: ${event.target.input}`);
}
return (
<>
<form onSubmit={handleSubmit}>
<Input label={props.label} />
<input type="submit" value="Submit"></input>
</form>
</>
)
}
I have that structure because I am defining what I want in my Form on the main HomePage component:
function Home() {
return (
<>
.......
<Section withForm label={["Name"]} {...homeObjFive}/>
<Section withForm label={"Phone"} {...homeObjOne}/>
.......
</>
)
}
This is the perfect case to use the useRef function from react.
In Form.js
import React, { useRef } from 'react'
create a reference constant and pass it as a prop into the input component. and change the input value that is handled in the onSubmit function to the reference
Also in Form.js (changes are made to the submit function)
function Form(props) {
const { inputValue } = useRef(); // added
const handleSubmit = (event) => {
event.preventDefault();
console.log(inputValue); // changed
console.log(props.label.value)
alert(`form is: ${event.target.input}`);
}
return (
<>
<form onSubmit={handleSubmit}>
{/* added the inputValue prop to the input component */}
<Input label={props.label} inputValue={inputValue} />
<input type="submit" value="Submit"></input>
</form>
</>
)
}
and now inside of the Input component set the input elements reference to the inputValue prop. you will no longer need a onChange function as reacts useRef function is updated automatically
In Input.js
function Input(props) {
return (
<label>
{props.label}
<input name={props.name} value={props.value} ref={props.inputValue}></input>
</label>
)
}
export { Input }
Suppose you have a form with two inputs, name and email (these are the id props of the inputs). You can extract the form values like this:
const handleSubmit = (event) =>
{
event.preventDefault()
const data = new FormData(event.currentTarget)
const name = data.get('name')
const email = data.get('email')
// do something with the data
}
You can read more about FormData here.
I trying to create a component with input type number, but i have a problem when my value change, the event onChange that input is not called, how i can fix this? because i have to pass the new value to the father component.
My code
//My Component
import React, { useState } from 'react';
import { AddCircle, RemoveCircle } from '#material-ui/icons'
import './inputnumber.css'
function InputNumber({ defaultValue, onClick, value, onInputChange }) {
const [Value, setVal] = useState(value|| 1)
function updateValue(value){
setVal(value)
return onInputChange(value)
}
function IncrementValue(){
setVal(Value + 1)
}
function DecrementValue(){
setVal(Value - 1)
}
return (
<div className='inputNumber-Container'>
<AddCircle onClick = {() => IncrementValue()} className='input-number-button'></AddCircle>
<input type='number' value = {Value} id = 'inputvalue' onChange = {(e) => updateValue(e.target.value)} ></input>
<RemoveCircle onClick = {() => DecrementValue()} className='input-number-button'></RemoveCircle>
</div>
)
}
export default InputNumber;
When i use the input and change the value manually its works, but when i try to use the buttons, my value update normaly but the event onChange don't pass to the father component the value updated.
You have both a local Value and a value from props. Your code right now doesn't work because you are not calling onInputChange to update the parent state from your IncrementValue and DecrementValue handlers. You only call onInputChange from the updateValue handler on the input onChange event.
It is not a good idea to duplicate props in state because it leads to out-of-sync values like this. You should remove the local value state and just rely on the parent's value. This is what we call a "controlled component" because it doesn't manage any state of its own.
Instead of calling setVal in your increment and decrement handlers you should call onInputChange to update the parent state.
function InputNumber({ defaultValue = 1, value, onInputChange }) {
function updateValue(value) {
onInputChange(value);
}
function IncrementValue() {
onInputChange(value + 1);
}
function DecrementValue() {
onInputChange(value - 1);
}
return (
<div className="inputNumber-Container">
<AddCircle
onClick={() => IncrementValue()}
className="input-number-button"
/>
<input
type="number"
value={value ?? defaultValue}
id="inputvalue"
onChange={(e) => updateValue(e.target.value)}
/>
<RemoveCircle
onClick={() => DecrementValue()}
className="input-number-button"
/>
</div>
);
}
Of course those handlers are simple enough that you can just write them inline.
function InputNumber({ defaultValue = 1, value, onInputChange }) {
return (
<div className="inputNumber-Container">
<AddCircle
onClick={() => onInputChange(value + 1)}
className="input-number-button"
/>
<input
type="number"
value={value ?? defaultValue}
id="inputvalue"
onChange={(e) => onInputChange(e.target.value)}
/>
<RemoveCircle
onClick={() => onInputChange(value - 1)}
className="input-number-button"
/>
</div>
);
}
With the below code
<div className="input-first-line-right">
<Input
type="textarea"
label="Project Description"
onChange={this.handleDescChange}
value={this.state.projectDescription}
/>
</div>
handleDescChange = text => {
if (!text) return;
this.setState({ projectDescription: text });
};
If handleDescChange is expecting an argument 'text' how come it is never passed.
Meaning why isn't the code
onChange={this.handleDescChange("some new text")}
inorder for the function to work. How does the code inherintly know what the parameter is if nothing is ever passed to it.
For onChange attribute, this.handleDescChange isn't called here.
Here, this.handleDescChange is given as callback. Input component calls this.handleDescChange when the change event is triggered.
If you want to pass a variable you can use fat arrow function. Solution is given below.
<div className="input-first-line-right">
<Input
type="textarea"
label="Project Description"
onChange={event => this.handleDescChange(event.target.value)}
value={this.state.projectDescription}
/>
</div>
handleDescChange = text => {
if (!text) return;
this.setState({ projectDescription: text });
};
This warning is triggered when we try to access to a React synthetic event in an asynchronous way. Because of the reuse, after the event callback has been invoked the synthetic event object will no longer exist so we cannot access its properties. source
The source link above have the correct answer but here is the summary:
Use event.persist()
Note: If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code. React documentation
class App extends Component {
constructor(props) {
super(props);
this.state = {
projectDescription: ""
};
}
handleDescChange = event => {
// Add here
event.persist();
// Don't check for '!text' Check if there is a value
if (event.target.value === 0) return;
this.setState({ projectDescription: event.target.value });
};
render() {
return (
<div className="input-first-line-right">
<input
type="textarea"
label="Project Description"
onChange={this.handleDescChange}
value={this.state.projectDescription}
/>
</div>
);
}
}
Though, I would recommend to start learning/use React hooks as it is more clean, elegant way to do this:
import React, { useState } from "react";
const App = () => {
// useState hook
const [projectDescription, setProjectDescription] = useState("");
const handleDescChange = event => {
if (event.target.value === 0) return;
setProjectDescription(event.target.value);
};
return (
<div className="input-first-line-right">
<input
type="textarea"
label="Project Description"
onChange={handleDescChange}
value={projectDescription}
/>
</div>
);
};