Creating a controlled form with Grommet throws errors - reactjs

I am trying to create a basic form using Grommet following the examples at https://v2.grommet.io/form. My specific form looks like this:
import React from 'react';
import { Box, Form, FormField, TextInput, Button } from 'grommet';
const defaultValue = {};
const LoginForm = () => {
const [value, setValue] = React.useState(defaultValue);
function handleSubmit(e) {
e.preventDefault();
const { email, password } = e.value;
console.log('pretending to log in:', email, password);
// doLogin(email, password)
}
return (
<Form
value={value}
onChange={nextValue => {
setValue(nextValue);
}}
onReset={() => setValue(defaultValue)}
onSubmit={handleSubmit}
>
<FormField label="email" name="email" required>
<TextInput name="email" />
</FormField>
<FormField label="password" name="password" required>
<TextInput name="password" />
</FormField>
<Box direction="row" justify="between" margin={{ top: 'medium' }}>
<Button type="reset" label="Reset" />
<Button type="submit" label="Login" primary />
</Box>
</Form>
);
};
As soon as I start typing into either field, I get the following:
Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: ...
Note that I get the exact same error if I replace my form code with a cut/paste from the example in the link above.
I have a fairly reasonable understanding of what the error means, but I have no idea how to fix it in this case. Is Grommet's implementation of controlled form components broken, or am I missing something elsewhere in my configuration or packages that might be causing this?

The React.js controlled standards are not allowing objects to be undefined.
So the problem starts with how you've defined your defaultValue = {};, since it is an empty object, there is no initial value to the FormField children and that causes them to be undefined and hence the error.
So if you'll change the preset value to be more accommodating to your fields, such as defaultValue = { password: '' }; it will fix your error.
For more reading about React controlled and uncontrolled inputs read this A component is changing an uncontrolled input of type text to be controlled error in ReactJS

Related

upload file with inertia react and laravel 9

im have problem with upload file with inertia react and laravel 9
i use this exact code but when i choose a file i got errors in console
https://inertiajs.com/file-uploads
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, 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
form
Create#http://127.0.0.1:5173/resources/js/Pages/Admin/Setting/Content/ContactUs/Create.jsx?t=1659389601569:28:14
s#http://127.0.0.1:5173/node_modules/.vite/deps/#inertiajs_inertia-react.js?v=0d793d15:741:16
The above error occurred in the <input> component:
input
form
Create#http://127.0.0.1:5173/resources/js/Pages/Admin/Setting/Content/ContactUs/Create.jsx?t=1659389680993:28:14
s#http://127.0.0.1:5173/node_modules/.vite/deps/#inertiajs_inertia-react.js?v=0d793d15:741:16
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
this is my code
import React from "react";
import Button from "#mui/material/Button";
import { Link, useForm, usePage } from "#inertiajs/inertia-react";
export default function Create() {
const { data, setData, post, progress } = useForm({
name: null,
avatar: null,
});
function submit(e) {
e.preventDefault();
post("/users");
}
return (
<form onSubmit={submit}>
<input
type="text"
value={data.name}
onChange={(e) => setData("name", e.target.value)}
/>
<input
type="file"
value={data.avatar}
onChange={(e) => setData("avatar", e.target.files[0])}
/>
{progress && (
<progress value={progress.percentage} max="100">
{progress.percentage}%
</progress>
)}
<button type="submit">Submit</button>
</form>
);
}
i think its problem with onChange={(e) => setData("avatar", e.target.files[0])}
I had the same issue... I solved removing the value attribute from the input type="file".
<input
type="file"
onChange={(e) => setData("avatar", e.target.files[0])}
/>
(If you see in the documentation the same example for svelte or vue, you will not see the value attribute there).

latest react-hook-form error handling with material-ui TextField

I have difficulties, using react-hook-form with material-ui.
I prepared a codesandbox example.
import { TextField } from "#material-ui/core";
import React from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
interface IMyForm {
vasarlo: string;
}
export default function App() {
const {
handleSubmit,
formState: { errors },
register
} = useForm<IMyForm>();
const onSubmit = (data: IMyForm) => {
alert(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>First Name</label>
<TextField
variant="outlined"
margin="none"
label="Test"
{...register("vasarlo", {
required: "error text"
})}
error={errors?.vasarlo ? true : false}
helperText={errors?.vasarlo ? errors.vasarlo.message : null}
/>
<input type="submit" />
</form>
);
}
How can I properly use the register function, to get error message, write to input field, also the onSubmit function to work?
I couldn't find answer in the documentation for this scenario.
In react-hook-form v7, this is how you register an input:
<input {...register('name')} />
Calling register() will return necessary props for your input like onChange, onBlur and ref. These props make it possible for react-hook-form to keep track of your form data. Now when you use register with Material-UI TextField like this:
<TextField {...register('name')} />
You pass the ref property directly to the TextField while the correct place to put it is in the inputRef:
<TextField inputRef={ref} />
So you have to modify your code like this:
const { ref: inputRef, ...inputProps } = register("vasarlo", {
required: "error text"
});
<TextField inputRef={inputRef} {...inputProps} />
How can I properly use the register function, to get error message
There is nothing wrong with your error handling code. Though you can shorten you code a bit more using Typescript's optional chaining operator ?.:
<TextField
error={!!errors.vasarlo}
helperText={errors?.vasarlo?.message}
inputRef={ref}
{...inputProps}
/>
Live Demo
You're misusing Controller. react-hook-form's default functionality is using uncontrolled inputs. Remove <Controller/> and put this on your TextField
inputRef={register({
required: 'This is required',
validate: (data) => myValidationFunction(data)
})}
You'll only want to use a controller if you NEED to modify/intercept/format a value that is being displayed in the TextField that is different from what a user is typing, i.e a phone number getting shown as (xxx)-xxx-xxxx when only typing the digits.

In this basic react application, why does the custom component `TextInput` allow me to type stuff in even though the regular `input` field doesn't?

I have a basic react form that I am testing out currently. It's still incomplete, but I have discovered unexpected behaviour.
The regular input field doesn't allow me to type in anything, as I am not updating the state yet. However, my custom TextInput component does allow me to type stuff in... Surprising, as I said before, I am not using setValues to update the state yet.
import React, { useState } from 'react';
const App = () => {
const [values, setValues] = useState({
firstName: '',
lastName: ''
});
return (
<div>
<form>
{/* [EXPECTED] this doesn't allow anything to be typed in at the front-end... which is expected...
... as I am not using `setValues` to update the state yet */}
<input
type="text"
id="first-name"
name="firstName"
value={values.firstName}
/>
{/* [NOT EXPECTED] this does allow stuff to be typed in at the front-end... which is strange...
... as I am not using `setValues` to update the state yet */}
<TextInput
id="last-name"
name="lastName"
value={values.lastName}
/>
<button type="submit">
Register
</button>
</form>
</div>
);
};
const TextInput = props => {
return (
<input
type="text"
id={props.id}
name={props.name}
/>
/* <span id={props.id + '-error'}>{props.title}</span> */
);
};
export default App;
Can anybody help me to explain why the difference?
Your first input is controlled - it has a value prop which is used to determine what the value of the element should be when rendered:
<input
type="text"
id="first-name"
name="firstName"
value={values.firstName}
/>
No matter what you type into it, since it's "controlled", the value that exists in it will always be what's currently in state as values.firstName.
In contrast, your second input is uncontrolled. It has no value prop:
<input
type="text"
id={props.id}
name={props.name}
/>
So, since you're not giving React any directives on what its value should be while being rendered, you can type whatever you want into it, and it won't be in conflict with React state.

How to set or clear value of material-ui Input in ReactJS

I am unable to clear the value of a material-ui Input using refs, not state.
I've tried both types of refs that I know about:
ref={this.input}
- and -
ref={el => (this.input = el)}
but neither seems to work w/ a material-ui Input
the following similar questions did not help:
How to get input value of TextField from Material UI?
Clear and reset form input fields
Clear an input field with Reactjs?
how to set Input value in formField ReactJs
Here's a snippet of my React JSX for the input & button:
<Input
type="text"
id="name"
inputComponent="input"
ref={el => (this.name = el)}
/>
<Button
variant="contained"
onClick={this.handleClear}
className="materialBtn"
>
Clear
</Button>
And the event handler that I expect should clear the input value:
handleClear() {
this.name.value = "";
}
I can make the code work fine using a standard HTML5 input, but not with a material-ui input, which is a requirement of this project. Additionally, this element's value is NOT in react state and I am not looking for a solution that requires using state -- I need to keep this piece as an uncontrolled component.
What am I missing w/ regard to material-ui? I have combed their docs/api but haven't found anything that suggests it needs to be handled differently from a standard input. thanks
Here's an example on CodeSandbox showing the failure w/ a material-ui input and success w/ an HTML5 input:
https://codesandbox.io/s/fancy-frost-joe03
I figured it out, you are using the wrong prop for the ref.
You should be using inputRef prop.
Here is the correct version,
<Input
type="text"
id="name"
inputComponent="input"
inputRef={el => this.name = el}
/>
<Button
variant="contained"
onClick={this.handleClear}
className="materialBtn"
>
Clear
</Button>
handleClear() {
this.name.value = "";
}
The reason is that the Material Input component creates an element with the following structure,
<div class="MuiInputBase-root MuiInput-root MuiInput-underline">
<input class="MuiInputBase-input MuiInput-input" id="name" type="text" value=""></input>
</div>
So, using ref would reference the root element which is <div>. So, they created a separate prop called inputRef to reference the child element <input>.
I updated your codesandbox.io code and saved it. Check out the full working code here,
https://codesandbox.io/s/elastic-dhawan-l4dtf
import React, { useState } from 'react'
import { Button, Container, InputBase } from '#material-ui/core'
const ClearText = ()=> {
const [text , setText] = useState("")
}
const clearTextField = () => setText("")
return (
<Container>
<InputBase
value={text ? text : ""}
onChange={(e)=>setText(e.target.value)}
/>
<Button onClick={clearTextField} > Clear </Button>
</Container>
)
};
export default ClearText;

Enzyme Shallow not finding a react-bootstrap Component

Below is a Sign In React SignIn Component made by using Redux-forms
const renderInput = ({input,label,type,placeholder}) => {
return (
<div>
<Form.Label>{label}</Form.Label>
<Form.Control type={type} placeholder={placeholder} { ...input}/>
</div>
)
}
export let signInForm = props => {
const { error,handleSubmit , pristine , submitting } = props
return (
<Container className="justify-content-md-center">
<Alert variant="primary">Sign in here if you already have an account</Alert>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Field name="email" component={renderInput} label="Email" type="email" placeholder="Email" />
</Form.Group>
<Form.Group>
<Field name="password" component={renderInput} label="Password" type="password" placeholder="Password" />
</Form.Group>
<Button type="submit" disabled= { pristine || submitting }>Sign In</Button>
</Form>
</Container>
)
}
export default signInForm = reduxForm ({
form : 'signIn'
})(signInForm)
My enzyme-shallow test for this
import React from 'react';
import { shallow } from 'enzyme';
import {signInForm as SignIn} from './SignIn';
import Button from 'react-bootstrap/Button'
import { expect } from 'chai';
describe('Test SignIn component', () => {
it('Test click event', () => {
const mockCallBack = jest.fn();
let wrapper = shallow(<SignIn onSubmit={mockCallBack}/>);
expect(wrapper.find(Button)).to.have.lengthOf(1);
})
})
My test output says
AssertionError: expected {} to have a length of 1 but got 0
1) The test fails. The Button component is not found in the test. I am expecting it to have a length of 1
2) I am using chai method to.Have.lengthOf because I could not get the jest method toHaveLength to work. toHaveLength seems to be used for only checking arrays or strings size. How could I use jest to do this?
If you are trying to simulate the SignIn form submit, you would actually call the simulate event on the form itself and not on the button.
You can simulate that with this code:
wrapper.find('form').simulate('submit');
Here is some info on why that is from the Enzyme docs:
Currently, event simulation for the shallow renderer does not propagate as one would normally expect in a real environment. As a
result, one must call .simulate() on the actual node that has the
event handler set.
Even though the name would imply this simulates an actual event, .simulate() will in fact target the component's prop based on the
event you give it. For example, .simulate('click') will actually get
the onClick prop and call it.
As noted in the function signature above passing a mock event is optional. Keep in mind that if the code you are testing uses the
event for something like, calling event.preventDefault() or accessing
any of its properties you must provide a mock event object with the
properties your code requires.
https://airbnb.io/enzyme/docs/api/ShallowWrapper/simulate.html

Resources