React form + localstorage won't update values - reactjs

Currently I am working on a form with the use of react hook forms and I want to save input from a user in the localstorage. At this moment whenever I change the value of the input when loaded from localstorage the input wont change.
When I console.log the event from the onchange method I don't get a log so it didn't trigger the onchange and thats where my problem is. I want that when a user wants to change anything in the form that it should update again.
I have searched on google but I can't find any related problem and so no solution. Since I am a beginner in react I have no clue to solve it myself.
The onchange function is in a functional component same as the input components is only functional.
This is the onchange function that contains the input event.
const onChange = event => {
localStorage.setItem(event.target.id, event.target.value);
};
This is the input compontent
<Input key={index} field={item} formFunction={register({required:true})} checkChange={handleChange} onChange={e => onChange(e)} value={localStorage.getItem(item.label) || ''} errors={errors} selectCheck={handleCountryChange} touched={touched} />
And this is the input compontents code
return (
<div className={props.field.grid}>
<div className={props.field.inputClass}>
<input
type={props.field.type}
name={props.field.name}
className={props.field.class}
id={props.field.label}
data-save="persist"
onBlur={props.checkChange}
style={{ marginBottom: '5px' }}
onChange={props.onChange}
value={props.value}
/>
<label className="left" htmlFor={props.field.label}>{props.field.label}</label>
</div>
</div>
);

Your problem is you are using the local storage to try and update the state of the app so the render function will not get re called and display the new inputted. ( unless you can show more code and you are indeed updating that this.props.value )?
I would suggest looking up local state within component for react, it will make things 10x easier in the future:
React state
Functional component state
You are best creating a local state in your constructor if it is an class component e.g., same can be achieved if it is a functional component just slightly different.
this.state = {
inputVariable: ""
}
then when ever your change this variable(in your onchange function using set state):
setstate({
inputVariable: valueToUpdate
})
your input components value field should be populated with this.state.inputVariable, so as you change the value it will trigger on change and then update the state which will cause a re render of your UI.
if you additionally also to save it to local storage you can do so like you already have.

Related

React-hook-form - Function components cannot be given refs

When trying to create a reusable input component that accepts a register react-hook-form Register function I get the error:
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
Following that, and solutions similar to this I ended up with this, however as you can see when clicking submit the values don't seem to get updated on the form.
I assumed this might've been because react-hook-form's internal ref is different from the one being forwarded, but looking at the docs it doesn't seem like there's a way to pass a ref.
Seeing how react-number-format binds well using a Controller I also tried that, but it still doesn't seem to work.
How would I go about creating this reusable component that can bind to different forms?
In your App.tsx file, change TextInput props as below.
<TextInput
placeholder="Text Input"
errors={errors.testInput}
register={register}
name={"testInput"}
options={ { required: true }}
// {...register("testInput", { required: true })} Don't destructure register here
/>
If register is destructured, then ref is passed directly to TextInput which is a function component and thus error in console.
register docs: https://react-hook-form.com/api/useform/register
No need to use Controller for native components. But in your code for ControlInput to work, pass onChange from field to input's onChange attribute.
render={({ field: { onChange, name, value } }) => (
<input
id={name}
// name={name}
type={fieldType}
placeholder={placeholder}
value={value||''}
onChange={onChange}
/>
)}

React Controlled Component

import React, { useState } from "react";
function App() {
const [name, setName] = useState("");
const [headingText, setHeadingText] = useState("");
function handleChange(event) {
// console.log(event.target.value);
setName(event.target.value);
}
function handleClick(event) {
setHeadingText(name);
//after everything is done
event.preventDefault();
}
return (
<div className="container">
<h1>Hello {headingText}</h1>
<form onSubmit={handleClick}>
<input
onChange={handleChange}
type="text"
placeholder="What's your name?"
value={name}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default App;
I will use the above code for asking my question.
input element handles its state change via user input(value attribute) but react uses state variable to handle state change. In order to have one constant value we set react variable to handle state change of form element as well.
value={name}
but if we do so and then access the value entered by the user through event.target.value, How is it able to track what user enters in the input field as we have set value={name}. Shouldn't event.target.value give us the value of name variable instead of user input
I think like this.
value={name} is external state for input element. In React term it's props.
And event.target.value comes from the internal state of input element right after the user keypress happens. In react terms it's a state.
So you change your internal state from user input. It's event.target.value
And you set input element's prop when parent component rerenders. it's value={name}
The answer is that react is 1 way data flow. It's not bound both ways like angularjs or Angular [(ngModel)] is. Meaning that internal DOM element state updates do not take effect until the component is re-rendered - and the component will not re-render until the component state is changed. Therefore, the value in the input is not being "set" by react until state changes. You can modify the value in the input as much as you'd like, and react won't do anything until the onChange handler that then updates state is triggered. Only then will it re-render the component and put the new value in the input box.
Until the re-render happens, the react state and the internal state of the <input> element are out of sync. event.target.value reads back the internal state of the input element, whereas {name} reads back the value of the name property on the, in this case, App component. The two get resync'd when the component is re-rendered.

React warning uncontrolled component in child component

I have child form and input components that are rendered within a parent project component. In the parent component, they are "uncontrolled" as in their value isn't initially set by the parent project's state. Their value is set by the form component state immediately. React keeps giving me this "Warning" (which registers as an error in the console).
Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
I understand the issue with controlled vs non controlled components. But how do I get React to see that the child is controlling the input rather than the parent and stop giving me this error?
Other relevant info: this is an external forms module that I created because my apps use a lot of forms and I wanted a standardized way to implement this with just an "npm install ...". This is the sudo code
<Parent project module>
//external forms components
<Form>
<Input/>
<Input/>
<Button/>
</Form>
//end external forms components
</Parent project module>
Here is the render method in the Form component that allows me to control the Input state from withing the Form component. Please let me know if other code snippets or other info would be helpful. Am I going about this incorrectly or is there some way to get rid of these errors? Everything works fine, but it clutters my console and makes it difficult to code.
render() {
const inputs = React.Children.map(this.props.children, child =>
React.cloneElement(child, {
value: this.state.value,
onChange: this.onChange,
error: this.state.userNotify[child.props.name]
})
);
return (
<div id="form-container">
<p className="formTitle">{this.props.formTitle}</p>
<form id={this.props.formID} onSubmit={this.onSubmit} >
{inputs} {/*there must be nested input components passed in*/}
</form>
</div>
)
};
You could try assigning a fallback value to your input components when their state value is not set yet.
const inputs = React.Children.map(this.props.children, child =>
React.cloneElement(child, {
value: this.state.value || '',
onChange: this.onChange,
error: this.state.userNotify[child.props.name]
})
);
This ensures that the value will never be undefined or null thus removing those console warnings.
You must be having value intialized in state
Like in constructor
this.state = {
value: null
}
Or on class level
state = {
value: null
}
So change value: null to value: ""
Thank you both for your suggestions. While they were not solutions in this case, they helped me to think through and identify the problem.
I have a prop on the Input component called prepPopVal. I use it to pre-populate the input with some value. For example; when viewing existing data, you can use my Form/Input component to display that data. It looks like this in the parent project
<Parent project module>
//external forms components
<Form>
<Input prePopVal={this.state.someValue}/>
<Button/>
</Form>
//end external forms components
</Parent project module>
I was using a conditional like this
if(typeof this.props.prePopVal === 'undefined')
var value = this.props.value;
else
var value = this.props.prePopVal;
then placing the "value" variable as the value of the html input
<input className="textinput"
type={type}
id={this.props.name}
name={this.props.name}
value={value}
onChange={this.props.onChange}
autoComplete="off"
/>
What I should have done is eliminated the conditional logic to set "value" and just used the "||" operator within the input to decide which to use, like this:
<input className="textinput"
type={type}
id={this.props.name}
name={this.props.name}
value={this.props.prePopVal || this.props.value}
onChange={this.props.onChange}
autoComplete="off"
/>
This way if "prePopVal" isn't defined it uses "value". This cleared my console error.
Thanks again. I hope this question is useful to someone.

Does React.js has similar modifier like Vue.js "v-model.lazy"

In Vue.js :
By default, v-model syncs the input with the data after each input event (with the exception of IME composition as stated above). You can add the lazy modifier to instead sync after change events:
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" >
Does react has the similar modifier or function?In which part of the official doc?
The following is from Vue docs for v-model:
Although a bit magical, v-model is essentially syntax sugar for
updating data on user input events
In React, you can listen to any input event ( onChange, onClick, etc. ) and trigger a function that updates the React Component's state. If you want to pass the data down, you can pass it as props to any children. In this way we can keep data updated with input events. For more info, see React State and React Component and Props
onChange (in React) will do exactly what v-model do in VueJS.
Vue <input type="text" v-model="record" />
React <input type="text" name="record" onChange={e => setRecord(e.target.value)} />
You can use onBlur event on input field in order to achieve functionality similar to v-model.lazy in VueJS.
Vue <input type="text" v-model.lazy="record" />
React <input type="text" name="record" onBlur={e => setRecord(e.target.value)} />
The .lazy modifier in Vue.js will sync on DOM's change events instead of input. DOM's change events occur in a variety of situations depending on the form element used. A check box or radio button will trigger a change event when it’s clicked. An input text box will trigger a change event when it loses focus. Different browsers might not trigger change events on the same interactions.
However, React's onChange works differently than onChange in the DOM: it’s more like DOM's onInput. React's onChange fires when there is a change in any of the form’s input elements, in contrast to the DOM’s change event.
In React.js, if you'd like to have the similar behaviour as Vue.js' v-model.lazy, you need to bind the event handler to a different event.
In regard to your example, in Vue.js we have:
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" >
In React.js, we cannot use onChange, as the onChange handler is fired on every key stroke, not just when the whole input field has changed. React.js' onChange is not "lazy" :-) Instead, you can use its onBlur event:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
msg: ''
}
}
handleBlur(event) {
this.setState({msg: event.target.value});
}
render() {
return <div>
<input type="text" onBlur={this.handleBlur.bind(this)}/>
<p>Your message: {this.state.msg}</p>
</div>
}
});
Here's the example on jsfiddle.

How to hide/show Field in FieldArray for Redux Form v6

given the fact in the example http://redux-form.com/6.0.5/examples/fieldArrays/. All the renderField.. functions are outside of the React Class. Hence how am i suppose to use react state or props to determine whether i want to hide or show a Field?
What I'm trying to do is to have a button to manipulate a state to display 'block' or 'none' for a Field. Can someone guide me? I tried to put the renderField variable inside the render of the React class, however this result in bugs.
All the props which are passed to Field are accessible to component props.
<Field
name={foo}
type="text"
component={TextField}
displayBlock={displayBlock}
/>
const TextField = props => {
if(props.displayBlock) {
...
}
return (
<div>
<input {...props.input} />
</div>
);
};
Thanks to Runaground who suggested an answer to me. I came to realized that I can pass in state as props to the FieldArray, such as
<FieldArray name="histories" component={renderHistories} openClose={this.state.openClose}/>
This allow me to utilize an array of state from this.state.openClose, to control which field i would like to hide or show.
<Field
name={`${histories}.details`}
type="text"
component={renderField}
style={{display: openClose[index] ? 'block' : 'none'}}
/>
However, even though I am able to control the display of the Field, the field array does not get rerendered.
Hence, I have to add on two functions
fields.push({});
setTimeout(()=>{
fields.pop();
}, 1);
that is taken from http://redux-form.com/6.0.5/docs/api/FieldArray.md/, to actually rerender the fieldarray whenever i hide or show the field. Hopefully there is a more elegant way to do this as the fieldarray does flicker.

Resources