React warning uncontrolled component in child component - reactjs

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.

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}
/>
)}

Not getting any console.log data from first name field box

import React, { useState } from "react";
import "../styles.css";
const FormBox = () => {
const [value, setValue] = useState({
fName: "",
lName: "",
});
const [textArea, setTextArea] = useState("");
const handleSumbit = (e) => {
e.preventDefault();
console.log(value);
console.log(textArea);
};
return (
<div className="center">
<form onSubmit={handleSumbit}>
<div>
{/*This is how to set a control form box */}
<input
placeholder="First Name"
type="text"
value={value.fName}
onChange={(e) => setValue(e.target.value)}
/>
</div>
<div>
{/*This is how to set a control form box */}
<input
placeholder="Last Name"
type="text"
value={value.lName}
onChange={(e) => setValue(e.target.value)}
/>
</div>
<div>
<textarea
value={textArea}
onChange={(e) => setTextArea(e.target.value)}
></textarea>
<div>
<input type="submit" />
</div>
</div>
</form>
</div>
);
};
export default FormBox;
I'm working on a basic React project and I'm currently getting the message "Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
input"
Another problem is that I'm not getting any info when typing in first name field box. I do get back last name and textarea.
Thank you so much and happy holiday
You are having two issues:
Not getting info from the first name field
The uncontrolled vs controlled warning
Issue 1.
This actually happens because on both the first name and last name inputs you are doing setValue(e.target.value). setValue() sets the object that is originally initialized as { fName: '', lName: '' }. This means that even though the value is an object at first, when writing on any of the fields, it will become a simple string, either the first or last name.
This problem does not happen with the text area, since you keep track of its value on a separate state, textArea. That's one way to fix your issue, hold both fName and lName on separate useState calls. Another way is to replace the old value object with a new one that works as expected, for example:
<input
placeholder="First Name"
type="text"
value={value.fName}
onChange={(e) => setValue({ ...value, fName: e.target.value })}
/>
I would also suggest avoiding abbreviations whenever possible. You could've used firstName and lastName instead as variable names. It really goes a long way improving readability.
Issue 2.
This warning is a side effect of the first issue. It appears because you are using controlled inputs but when writing on one of them the object is turned into a string and both value.fName and value.lName are undefined. Controlled inputs are not supposed to have undefined as a value, since that's usually an indicator that you want to use uncontrolled inputs. The empty value should be an empty string, which was intended and will be fixed if you fix the first issue.
There's another stack overflow sharing the differences between controlled and uncontrolled components: What are React controlled components and uncontrolled components?
A Controlled Component is one that takes its current value through props and notifies changes through callbacks like onChange. A parent component "controls" it by handling the callback and managing its own state and passing the new values as props to the controlled component. You could also call this a "dumb component".
A Uncontrolled Component is one that stores its own state internally, and you query the DOM using a ref to find its current value when you need it. This is a bit more like traditional HTML.
The important bit to note here is that an uncontrolled component doesn't receive a value prop and hence its value is undefined.

react-hook-form custom uncontrolled inputs don't update their meta state (dirty, touched)

EDIT: solved by a react-hook-form module update.
Problem
react-hook-form does not update form input state dirty, touched on custom components where name is dynamically passed to register. Specifically, field array inputs where the name is something like ingredients.0.description will track the input value but not the input state. Simple names like question or name seem to behave correctly.
Reproduction of the bug on this CodeSandbox.io environment.
Steps to reproduce:
Type or change the value in the "name" field (on the first line)
Type or change the value in any of the "description" fields (the field array)
Open react-hook-form DevTools (the pink icon in the bottom right of the browser)
See that the ingredients fields did not update dirty or touched values despite not matching defaultValue and having blurred off the input
See that the name field shows dirty: true, touched: true correctly
Context
I'm using react-hook-form and running into problems when integrating it with a custom uncontrolled component.
In most (all?) of the examples I see, the register function is either applied to a native HTML Input Element as one or the other:
<input {...register("field_name")} />
<input ref={register} name="field_name" />
Because I have some custom input styling, I'd like to make reusable components that integrate into react-hook-form, and though I could do something like:
// Input.tsx
export function Input(props) {
type Props = {
type: string;
name: string;
};
export function Input(props: Props) {
const { type, name } = props;
const { register, getValues, formState } = useFormContext();
return <input type={type} {...register(name)} />;
}
// App.tsx
<FormProvider {...methods} >
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="field_name" type="text" />
</form>
<FormProvider/>
Questions
Am I using register incorrectly?
Can register take a name dynamically or must it be a static string? Is my field array name description malformed?
Do uncontrolled component methods need to be applied directly to an <input /> or is it possible to wrap with a custom component and pass the props? What's the most idiomatic way to do this with react-hook-form?
How can I modify the code in my CodeSandbox to correctly reflect all field inputs' states?

React form + localstorage won't update values

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.

How to properly init jQuery module on React component?

I have parent React component called Sorter. Inside of Sorter I have child component for range slider:
<div className="child">
<rangeSlider props=... />
</div>
<rangeSlider props={...}/> returns simple input:
render() {
return <input type="text" id={this.props.id} name={this.props.id} value=''/>
}
And then I call module initialization via componentDidMount():
componentDidMount() {
jQuery('#' + this.props.id).ionRangeSlider(config)
}
And everything works fine until I actually use range slider. I have callback in config, which updates parent (in this case it's Sorter's) state. After that range slider just disappears. I tried to reinit it via componentDidUpdate() but it didn't do the trick.
Am I doing something wrong?
I have tried this in codesandbox:
https://codesandbox.io/s/summer-sea-imwpp?fontsize=14
As you can see, it's working fine.
Could you please provide the error message or more details?
Such as :
Is the RangeSlider has to update when the range changed?
How does Sorter change its state with the ionRangeSlider config?
Thanks.
I figured it out. Since I call <RangeSlider props={...} /> render with new props everytime, it triggers React to update DOM and to run re-render.
From the very beginning we have rendered only a single <input> element which has only static attributes that will never change:
<input type="text" id={this.props.id} name={this.props.id} value='' readOnly />
And ionRangeSlider, when called, prepends it's own DOM elements to this input and after this happens we don't need to interact with this input anyhow.
Thus we don't have to re-render it everytime new props arrives. So I just wrapped input in empty <div /> and prevented render from being executed:
shouldComponentUpdate() {
return false
}
render() {
return (
<div>
<input type="text" id={this.props.id} name={this.props.id} value='' readOnly />
</div>
)
}
Yet still I don't think it's a good practice to write dummy methods like this that only returns single static value, but as long as my rendered DOM won't change under no circumstances the situation is under control.

Resources