ReactJS: Save input value on blur and not on every key stroke - javascript

I have created a React View, say MyView, which has 2 text inputs whose initial values will be passed by parent read from a DB.
I also want the changed values to be saved back to DB. So, the view is also passed a callback function for the same.
Consider that DB save operation is heavy and you should not do it very frequently. So, I decided to listen to onBlur events instead of onChange events on the input boxes as onChange is invoked on every key stroke.
First Approach:
class MyView extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input type="url" value={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
<input type="url" value={this.props.values.B}
onBlur={(evt)=>{this.props.saveValue('B', evt.target.value)}} />
<button type="button" onClick={this.props.resetValues}>Reset</button>
</div>
);
}
}
However, this does not work as React enforces a controlled input (with value attribute) always to be accompanied by an onChange listener.
Second Approach:
So, I tried to make these inputs as uncontrolled. That is, instead of value attribute, used defaultValue.
<input type="url" defaultValue={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
But this also did not work as on reset/clear button click, although the view was made to re-render but defaultValue does not update once view is created.
Third Approach:
So, I finally added an onChange listener but as no-op.
<input type="url" value={this.props.values.A}
onChange={()=>{console.log('do nothing')}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
Again, this did not work as the view re-renders after calling onChange and since value is not reflected in props yet, value seems to reset back to initial on every key stroke.
Fourth Approach:
Last I tried was to maintain a state in component and read value from state and on every onChange save the value back to state. This worked to most extent but whenever there were external changes to props and the view was re-rendered, state did not update. So, I added a getDerivedStateFromProps function to view:
static getDerivedStateFromProps(props, state) {
return props.values;
}
Now, this again did not work. Reason being that this function is invoked even if I temporarily save values to state and the state was reset to initial values in props.
Can some ReactJS expert help me with my use-case?

You will still need onChange to help you set the states of both url input. onBlur is only used to trigger saving, it's 2 different events for different purposes.
Since your A & B values are passed down from parent component. MyView's parent component should pass down this.state.values and the functions to set the state.
Refer to this snippet if everything is in single component. You should be able move handleChange function up to its parent component.
class App extends React.Component {
state = {
values: {
A: '',
B: ''
}
}
handleChange = e => {
this.setState({
values: {
...this.state.values,
[e.target.name]: e.target.value
})
}
handleBlur = e => {
if (e.target.name === 'A') {
alert(`Saving A: ${this.state.values.A}`)
}
if (e.target.name === 'B') {
alert(`Saving B: ${this.state.values.B}`)
}
}
render() {
return (
<div>
<label>Value A</label>
<input
type="url"
name="A"
value={this.state.values.B}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<label>Value B</label>
<input
type="url"
name="B"
value={this.state.values.A}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
</div>
EDIT: Your fourth approach should work with the following:
static getDerivedStateFromProps(props, state) {
return { values: props.values }
}
constructor(props) {
super(props)
this.state = {
values: props.values
}
}
so basically the this.state.values is the final source of truth. When user types something, you setState in this component and change it. But if props.values changes (from external source), getDerivedStateFromProps will update the values state.

Going by the comments on Liren Yeo's solution, I would handle the props-state reconciliation on componentDidUpdate, where you get both the old state and props. This way you can determine how this.props was updated and act accordingly. When the value in props does not match state nor oldProps, the update is external and you should override the unsaved changes in the state.
The code should look something like this
componentDidUpdate(prevProps) {
if (this.props.values !== prevProps.values && this.props.values !== this.state.values) {
this.setState({values:this.props.values});
}
}
If you go this route, you can also leave the input uncontrolled and update its value through a reference. This solves some unreliability with controlled inputs, like for example, a type='number' returning undefined as its value when you type a decimal comma. You still need to store the value onChange but only save it onBlur and handling the state-prop-dom reconciliation in componentDidUpdate

So with the idea that onChange works, I would recommend you to take a look at this:
https://schier.co/blog/2014/12/08/wait-for-user-to-stop-typing-using-javascript.html
Navigate to the heading: Wait for Typing to Stop
Hope it can somehow lead you to what you want to achieve.

Related

Why is this react handleChange not working? Trying to handle a change received by props

I have an input field in react, whose value is initially set by a props received by the component
<EditUserModal
show={this.state.showModal}
close={this.closeModal}
userId={this.state.rowId}
email={this.state.selectedMail}
/>
As you can see it received the value email, and inside the component I can see the valu changing properly
<div class="form-group emailgroup">
<label for="exampleFormControlInput1">Change Email Address</label>
<input
type="email"
class="form-control"
placeholder="name#example.com"
value={this.props.email}
onChange={this.handleChange}
/>
</div>
Problem is, that I add a handleChange event, to be able to modify that input, but it doesnt modify the email, I suppose that its because I should change the props in the parent component, but I dont understand how to do it. Currently my handleChange function looks like this.
handleChange = (evt) => {
this.setState({
email: evt.target.value
});
}
And this is the initial state fo the component
this.state = {
email : ""
};
Obviously since im not calling the state, but the props to asign the value of the field, it doesnt change. But ive tried....
this.state = {
email : props.email
};
And it lets me change it this way, but the problem is that it will render nothing initially in the input value.
Don't assign your props to state, instead call your function from parent and pass state value to child component.
you can refer this for better understanding of parent to child data flow.
How to change the state of a child component from its parent in React
You are passing the wrong value into the component. Current you have this:
email={this.state.selectedEmail}
But when you look at what you're setting in the parent component, it's not 'selectedEmail' but 'email'. Changing the property should fix it:
email={this.state.email}
Then the correct value should be worked on.

Keeping focus in child component after render

I have an input field in React, where I set the global name for my application.
I have the handlers set in the parent component called App.js
....
state = {
name: 'app_x',
isProcessedData: false,
isUploadedFiles: false,
isProcessedDBScan: false
}
setNameHandler = (e) => {
this.setState({name: e.target.value});
}
...
in my child component, I refer to the state and the handler via props
export default function Home(props) {
console.log(props)
return (
<div>
<p>Welcome to our application</p>
<TextField
id="outlined-name"
label="Name"
className={"textfiled"}
value={props.name}
onChange={props.handleChange} //the textfield loses focus after each time the input is changed
margin="normal"
variant="outlined"
/>
</div>
)
}
after each keystroke, the text field, is not focused anymore, because the entire UI is rendered.
I tried using React.useState("app_x")
for testing purposes, which worked fine, as long it's inside the component, but I need to pass down the state of the application to other components.
Is there a way to solve this?
I thought about using refs, and then passing a ref down as a callback instead, but as far as the most use cases I have looked at it's mostly relevant inside the same component?

reactjs retain field props

how to retain component props after emitting back event.
Desc: i have a container it has multiple components (in a same view) in one of component has two fields and one button "continue". After entering some values into those fields , clicking on continue button its going to another component in this component we have two fields and two buttons one is back and continue when i click on back button its going to previous component but not retaining the props which entered on those . can you help me how to retain data.
The simplest solution for you is to have a parent component which holds all the data in its state. Parent container component passes the data to the children components you have as props, also it passes the callbacks to so a child can update the data. Given that you parent is always rendered and thus is never unmounted the data will be always there.
Something like this:
class Parent extends React.Component {
state = { name: 'John' }
render() {
return <div>
<Child name={this.state.name} onNameChange={e => this.setState({name: e.target.value})} />
</div>
}
}
class Child extends React.Component {
render() {
return <div>
<p>The name "{this.props.name}" is saved in the Parent</p>
<input type="text" value={this.props.name} onChange={this.props.onNameChange} />
</div>
}
}
A more complicated solution is to use something like Flux or Redux, but I believe it will be a bit too much for you now.

Is there a way to render only after only a complete input? Delaying render() - ReactJS

I have some Controlled Component text inputs for a form:
<input type="text" onChange={(e) => this.props.changeBusiness(e)}/>
I also want to render the (above) text input in a separate component - however! It would be nice if I could render it with the COMPLETED text input to give (in my opinion), what would be a better feel.
How might this be implemented? I assume I could have an onBlur
handler that is triggered.
Is there any way to delay the render() till only after the complete text input?
Danke
Yes if you want to delay a render you just need to return null in the render based on a flag. Lets say you have a state variable inputBlurred that you pass around as a prop to something else.
render() {
if (!this.props.inputBlurred) return null;
... more here
}
You would use it like so.
class ParentComponent extends Component {
constructor() {
super();
this.state = {inputBlurred: false}
}
handleBlur = (e) => {
if(e.target.value){
this.setState({inputBlurred: true});
}
}
render() {
return(
<div>
<input onBlur={this.handleBlur} />
<ChildComponent inputBlurred={this.state.inputBlurred} />
</div>
)
}
}
You have several posibilities.
Create custom Input wrapper around input component, which does trigger "change" event on "complete" change - e.g. on blur. While you typing, parent component won't receive incremental onChange events.
From parent, pass onBlur handler to child input component and as soon, as onBlur handler is triggered, render input's value wherever you wish.

React - Why is input field still readonly (i.e. not editable) even though I've added onChange?

I know this question has been asked before, but I've tried all of solutions I could find, and after wasting days on this, I'm literally about cry. What am I doing wrong?
The input field remains readonly and onChange won't fire despite my varied and increasingly desperate attempts to coax it into behaving like the most basic of text input fields.
here's my code:
import React, { Component, PropTypes } from 'react';
export default class Contact extends Component {
constructor(props) {
super(props);
this.state = { name: '' };
}
handleChange(e) {
this.setState ({ name: e.target.value });
}
render() {
return (
<div>
<form>
<input
type = "text"
value = { this.state.name }
onChange = { this.handleChange.bind(this) }
/>
</form>
</div>
);
}
}
EDIT: I just realized that it does work as expected as long as I add <Contact /> to a component that doesn't invoke componentDidMount(). It would be super helpful if someone could give a breakdown of why that would make input fields readonly, i.e. does invoking componentDidMount() somehow interfere with onChangeor setState?
EDIT 2: componentDidMount() only appeared to be the issue because the components where I invoked it were the ones that contained transitions/animations which I'd attempted to layer using z-index. It turns out that a negative z-index on a parent div can disable an input field by preventing you from being able to click into the text field, even if the input appears completely unobscured.
To, fixed this you need to replace value as defaultValue so that you can change the value property of input field
import React, { Component, PropTypes } from 'react';
export default class Contact extends Component {
constructor(props) {
super(props);
this.state = { name: '' };
}
handleChange(e) {
console.log(e.target.value); // make sure you are receiving the the value
this.setState ({ name: e.target.value });
}
render() {
return (
<div>
<form>
<input
type = "text"
defaultValue = { this.state.name } // here write defaultValue instead of value
onChange = { this.handleChange.bind(this) }
/>
</form>
</div>
);
}
}
The z-index of parent divs can make input fields appear readonly by making it impossible for you to click into the field. This can happen even if the field appears completely unobscured. http://jsfiddle.net/t8542vcy/2/
In my code, a very distant parent div had a negative z-index, causing the input to silent fail. It functioned normally once I adjusted the z-index. http://jsfiddle.net/t8542vcy/5/
I had no idea that z-index could cause this and wasted SO much time writing and rewriting my code because I incorrectly thought it was a React issue. I shed literal tears and nearly lost the last shreds of my sanity. I can only hope that this helps someone tormented by a similar demon.
Check your z-index, folks, check your z-index.

Categories