Textfield onChange not works with react and MobX - reactjs

I have a textField which are be controlled by a MobX store, but if I enter something to the textfield only on letter will change. Here my code. There is my mistake?
I made my component as observer and my Store is makeAutoObservable inside the constructor.
class SettingsStore {
constructor() {
makeAutoObservable(this)
makePersistable(this, {
name: 'SampleStore',
properties: ['roadmapDescription'],
storage: window.localStorage
})
}
async onChangeRoadmapTitle(name: string): Promise<void> {
setTimeout(() => {
console.log('input', name)
this.roadmapDescription.title = name
console.log(
'this.roadmapDescription.title',
this.roadmapDescription.title
)
}, 50)
}
}
Here is my React Code
const SettingsGeneral: React.FC<ISettingsGeneral> = observer(({ onSubmit }) => {
return (
<div>
<Form<{ title: string; description: string; dataSource: string }>
onSubmit={(data) => onSubmit(data)}
>
{({ formProps = 'test', submitting }) => (
<form {...formProps}>
<FormSection>
<Field
aria-required={true}
name='title'
label='Name of the Roadmap'
isRequired
>
{({ fieldProps }) => (
<Fragment>
<TextField
testId='roadmap-title-text-field'
autoComplete='off'
{...fieldProps}
className='w-48'
onChange={async (
e: React.ChangeEvent<HTMLInputElement>
) =>
await settingsStore.onChangeRoadmapTitle(e.target.value)
}
value={settingsStore.roadmapDescription.title}
/>
</Fragment>
)}
</Field>
</form>
)}
</Form>
</div>
)
})

The mistake was I use value and not defaultVaule
Correct solution is:
<Fragment>
<TextField
testId='roadmap-title-text-field'
autoComplete='off'
{...fieldProps}
className='w-48'
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
settingsStore.onChangeRoadmapTitle(e.target.value)
}
defaultValue={settingsStore.roadmapDescription.title}
/>
</Fragment>

Related

Is it possible to simple-react-code-editor as a Formik field component?

Trying to get this form field component to take a simple-react-code-editor:
not sure if I'm going about this the right way by trying to pass props form the useField hook, but it works for textfield tags, so thought the same method could apply to this as well. Although, I get the feeling the onValueChange callback is different from the onChange callback that this component doesn't have. Is there a way to add it somehow?
Editor Component:
const MyEditor = ({value, onChange}) => {
const highlight = (value) => {
return(
<Highlight {...defaultProps} theme={theme} code={value} language="sql">
{({ tokens, getLineProps, getTokenProps }) => (
<>
{tokens.map((line, i) => (
<div {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span {...getTokenProps({ token, key })} />
))}
</div>
))}
</>
)}
</Highlight>
)
};
return (
<Editor
value={value}
onValueChange={onChange}
highlight={highlight}
padding={'40px'}
style={styles.root}
/>
);
}
export default MyEditor;
Form with Field Component as MyEditor (tried using useField hook):
const FormQueryTextBox = ({...props}) => {
const [field] = useField(props);
return (
<MyEditor onChange={field.onChange} value={field.value}/>
)
}
const validationSchema = yup.object({
query_name: yup
.string()
.required()
.max(50)
});
const AddQueryForm = () => {
return (
<div>
<Formik
validateOnChange={true}
initialValues={{
query:""
}}
validationSchema={validationSchema}
onSubmit={(data, { setSubmitting }) => {
console.log(data);
}}
>
{() => (
<Form>
<div>
<Field
placeholder="query name"
name="query_name"
type="input"
as={TextField}
/>
</div>
<div>
<Field
name="query"
type="input"
as={FormQueryTextBox}
/>
</div>
</Form>
)}
</Formik>
</div>
)
}
components render without errors, but as I type the text doesn't appear.
I figured out that I just need to customize my onChange with the setter from the useFormikContext hook like this:
const FormQueryTextBox = ({...props}) => {
const [field] = useField(props);
const { setFieldValue } = useFormikContext();
return (
<MyEditor {...field} {...props} onChange={val => {
setFieldValue(field.name, val)
}}/>
)
}

How to set value of a Select in react-hook-form?

I am trying to load async data and use it to populate material-ui components in a form with react-hook-form. I have a TextField that seems to work fine, but I can't seem to figure out how to get the Select to show the correct value.
Here's a codesandbox to demo my problem.
I am using Controller to manage the Select as seems to be recommended in the docs:
const { register, handleSubmit, control, reset, setValue } = useForm()
<TextField name="name" inputRef={register} />
<Controller
name="color_id"
control={control}
register={register}
setValue={setValue}
as={
<Select>
{thingColors.map((tc, index) => (
<MenuItem key={index} value={tc.id}>
{tc.name}
</MenuItem>
))}
</Select>
}
/>
I'm trying to populate the fields with reset from useForm(), which seems to work for the TextField.
useEffect(() => {
getData().then((result) => {
reset({
color_id: 3,
name: 'Bill'
});
});
}, [reset]);
This seems to correctly set the values for the form, and when I submit my form it seems to have the correct values for name and for color_id. It seems like I'm not correctly hooking up the Select and the control is not showing the selected value that I set.
How can I get my material UI Select to show my applied value here?
In the version 7 of react hook form you can use setValue() setvalue API
useEffect(() => {
getData().then((result) => {
setValue('color_id', '3', { shouldValidate: true })
setValue('name', 'Bill', { shouldValidate: true })
});
}, []);
Note than I use the shouldValidate,this is becuase I use the isValidated in the button like this:
<Button
handler={handleSubmit(handlerSignInButton)}
disable={!isValid || isSubmitting}
label={"Guardar"}
/>
With shouldValidate I revalidate the inputs, There is also isDirty.
In version 7 of react hook form, you should use render instead of Controller API
<Controller
control={control}
name="test"
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, isTouched, isDirty, error },
formState,
}) => (
<Checkbox
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
Or you can use reset reset API
useEffect(() => {
getData().then((result) => {
reset({
'color_id': '3',
'name': 'Bill'
)
});
}, []);
I have not used Material UI with react hook form, but hope this is helpful.
A example of my select component, in Ionic React Typescript:
import { ErrorMessage } from "#hookform/error-message";
import { IonItem, IonLabel, IonSelect, IonSelectOption } from
"#ionic/react";
import { FunctionComponent } from "react";
import { Controller } from "react-hook-form";
type Opcion = {
label: string;
value: string;
};
interface Props {
control: any;
errors: any;
defaultValue: any;
name: string;
label: string;
opciones: Opcion[];
}
const Select: FunctionComponent<Props> = ({
opciones,
control,
errors,
defaultValue,
name,
label
}) => {
return (
<>
<IonItem className="mb-4">
<IonLabel position="floating" color="primary">
{label}
</IonLabel>
<Controller
render={({ field: { onChange, value } }) => (
<IonSelect
value={value}
onIonChange={onChange}
interface="action-sheet"
className="mt-2"
>
{opciones.map((opcion) => {
return (
<IonSelectOption value={opcion.value}
key={opcion.value}
>
{opcion.label}
</IonSelectOption>
);
})}
</IonSelect>
)}
control={control}
name={name}
defaultValue={defaultValue}
rules={{
required: "Este campo es obligatorio",
}}
/>
</IonItem>
<ErrorMessage
errors={errors}
name={name}
as={<div className="text-red-600 px-6" />}
/>
</>
);
};
export default Select;
And its implementation:
import React, { useEffect } from "react";
import Select from "components/Select/Select";
import { useForm } from "react-hook-form";
import Server from "server";
interface IData {
age: String;
}
let defaultValues = {
age: ""
}
const rulesEdad= {
required: "Este campo es obligatorio",
}
const opcionesEdad = [
{value: "1", label: "18-30"},
{value: "2", label: "30-40"},
{value: "3", label: "40-50"},
{value: "4", label: "50+"}
]
const SelectExample: React.FC = () => {
const {
control,
handleSubmit,
setValue,
formState: { isSubmitting, isValid, errors },
} = useForm<IData>({
defaultValues: defaultValues,
mode: "onChange",
});
/**
*
* #param data
*/
const handlerButton = async (data: IData) => {
console.log(data);
};
useEffect(() => {
Server.getUserData()
.then((response) => {
setValue('age', response.age, { shouldValidate: true })
}
}, [])
return (
<form>
<Select control={control} errors={errors}
defaultValue={defaultValues.age} opciones={opcionesEdad}
name={age} label={Edad} rules={rulesEdad}
/>
<button
onClick={handleSubmit(handlerSignInButton)}
disable={!isValid || isSubmitting}
>
Guardar
</button>
</form>
In React Hook Form the Select field have a "key/value" response.
So you should use:
setValue(field-name, {label: 'your-label' , value: 'your-value'});
Referring to https://github.com/react-hook-form/react-hook-form/discussions/8544
You need the Select to be wrapped with Controller and be sure to put a defaultValue on the Controller.
Example: https://codesandbox.io/s/admiring-curie-stss8q?file=/src/App.js
You can do something like this:
const Form: FC = () => {
const { register, handleSubmit, control, reset, setValue } = useForm();
const [color, setColor] = useState({name:"", color_id:-1})
useEffect(() => {
getData().then((result) => {
console.log("Got thing data", { result });
reset({
color_id: result.optionId,
name: result.name
});
setColor( {color_id: result.optionId,
name: result.name});
});
}, [reset]);
const onSubmit = (data: any) => console.log("Form submit:", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div style={{ width: "200px" }}>
<div>
<TextField
fullWidth
name="name"
placeholder="Name"
inputRef={register}
/>
</div>
<div>
<Controller
name="color_id"
control={control}
register={register}
setValue={setValue}
defaultValue={color.name}
as={
<Select value="name" name="color_id" fullWidth>
{thingColors.map((tc, index) => (
<MenuItem key={index} value={tc.id}>
{tc.name}
</MenuItem>
))}
</Select>
}
/>
</div>
<p></p>
<button type="submit">Submit</button>
</div>
</form>
);
};
you can use a useState() to control the default value that you fetch with the getData() method and then pass the state to defaultValue param in the Controller.

Using Material UI's Autocomplete using Formik to display different value in dropdown but set different value in formik state

I am trying to use Material UI's Autocomplete with Formik. Here is a custom Autocomplete component I wrote to use with Formik.
import React from "react";
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
import { fieldToTextField } from "formik-material-ui";
const FormikAutocomplete = ({ textFieldProps, ...props }) => {
const {
form: { setTouched, setFieldValue },
} = props;
const { error, helperText, ...field } = fieldToTextField(props);
const { name } = field;
return (
<Autocomplete
{...props}
{...field}
onChange={(_, value) =>
setFieldValue(name, value)
}
onBlur={() => setTouched({ [name]: true })}
renderInput={(props) => (
<TextField
{...props}
{...textFieldProps}
helperText={helperText}
error={error}
/>
)}
/>
);
};
export default FormikAutocomplete;
Here is how components callled
<Field
name="title"
component={FormikAutocomplete}
options={gender}
getOptionLabel={(option) => option.title_name_long}
textFieldProps={{
label: "Title",
required: true,
variant: "outlined",
margin: "dense",
}}
/>
Now what I intend to do is: If I have a object like
gender=[{gender_name_short:"F",gender_name_long:"Female},{gender_name_short:"M",gender_name_long:"Male}]
I want the Autocomplete dropdown to show options male,female.
But I want the formik state to save M,F respectively once selected from dropdown. Currently the entire object gets saved.
How can this be done?
In FormikAutocomplete component,
use setFieldValue in the onChange of autocomplete
use gender_name_long in getOptionLabel to display Male , Female
use gender_name_short in getOptionSelected to use M or F
With that, finally, when you submit, you will get to see M/F not Male/Female
Working demo
const gender = [
{ gender_name_short: "F", gender_name_long: "Female" },
{ gender_name_short: "M", gender_name_long: "Male" }
];
const validationSchema = object().shape({
// genderObj: array().required("At least one gender is required")
});
const initialValues = {
username: "abc",
country: "",
gender: "M",
birthdate: null
};
const FormikAutocomplete = ({ textFieldProps, ...props }) => {
const {
form: { setTouched, setFieldValue }
} = props;
const { error, helperText, ...field } = fieldToTextField(props);
const { name } = field;
return (
<Autocomplete
{...field}
{...props}
onChange={(_, data) => {
setFieldValue("gender", data.gender_name_short);
}}
onBlur={() => setTouched({ [name]: true })}
// getOptionLabel={item => item.gender_name_long} //<----see here
getOptionLabel={item => {
// console.log( '====>' , typeof item === "string" ? props.options.find(i => i.gender_name_short === item).gender_name_long : item.gender_name_long)
return typeof item === "string"
? props.options.find(i => i.gender_name_short === item)
.gender_name_long
: item.gender_name_long;
}}
// getOptionLabel={item => typeof item === 'string' ? props.options.find(i => i.gender_name_short === item).gender_name_long : item.gender_name_long}
getOptionSelected={(item, current) => {
return item.gender_name_short === current;
}}
// defaultValue={'hi'}
renderInput={props => (
<TextField
{...props}
{...textFieldProps}
helperText={helperText}
error={error}
/>
)}
/>
);
};
const SimpleFormExample = () => (
<div>
<h1>Simple Form Example</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
validateOnBlur={false}
validateOnChange
onSubmit={(values, { resetForm, setSubmitting }) => {
console.log(values);
resetForm();
// setSubmitting(false);
}}
>
{formik => (
<Form>
<Field
name="gender"
component={FormikAutocomplete}
label="gender"
options={gender}
textFieldProps={{
fullWidth: true,
margin: "normal",
variant: "outlined"
}}
// multiple
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
export default SimpleFormExample;

How do I access current value of a formik field without submitting?

How do I access value of the SelectField named countryCode in my React component? Use case is that validation scheme should change according to the countryCode.
<Formik
onSubmit={(values, actions) => this.onSubmit(values, actions.setFieldError)}
validationSchema={() => this.registrationValidationSchema()}
enableReinitialize={true}
initialValues={this.props.initialData}
>
<Form>
<Field
name="countryCode"
component={SelectField}
label="Country"
labelClassName="required"
options={Object.entries(sortedCountryList).map(x => ({
value: x[1][1],
label: x[1][0]
}))}
/>
</Form>
</Formik>
I have tried to access it via a ref, then via this.props.values (as suggested in getFieldValue or similar in Formik) but both return just undefined or null. My props don't have any "values" field.
EDIT: Found an ugly way: document.getElementsByName("countryCode")[0].value. A better way is appreciated.
You can use ref, if you need the values outside formik
const ref = useRef(null);
const someFuncton = () => {
console.log(ref.current.values)
}
<Formik
innerRef={ref}
onSubmit={(values, actions) => this.onSubmit(values,
actions.setFieldError)}
validationSchema={() => this.registrationValidationSchema()}
enableReinitialize={true}
initialValues={this.props.initialData}
/>
<form></form>
</Formik>
You can access values like this:
<Formik
onSubmit={(values, actions) => this.onSubmit(values,
actions.setFieldError)}
validationSchema={() => this.registrationValidationSchema()}
enableReinitialize={true}
initialValues={this.props.initialData}
>
{({
setFieldValue,
setFieldTouched,
values,
errors,
touched,
}) => (
<Form className="av-tooltip tooltip-label-right">
// here you can access to values
{this.outsideVariable = values.countryCode}
</Form>
)}
</Formik>
you can get it from formik using the Field comp as a wrapper
import React, { ReactNode } from 'react';
import { Field, FieldProps } from 'formik';
(...other stuffs)
const CustomField = ({
field,
form,
...props
}) => {
const currentError = form.errors[field.name];
const currentField = field.name; <------ THIS
const handleChange = (value) => {
const formattedDate = formatISODate(value);
form.setFieldValue(field.name, formattedDate, true);
};
const handleError = (error: ReactNode) => {
if (error !== currentError) {
form.setFieldError(field.name, `${error}`);
}
};
return (
<TextField
name={field.name}
value={field.value}
variant="outlined"
helperText={currentError || 'happy helper text here'}
error={Boolean(currentError)}
onError={handleError}
onChange={handleChange}
InputLabelProps={{
shrink: true,
}}
inputProps={{
'data-testid': `${field.name}-test`, <---- very helpful for testing
}}
{...props}
/>
</MuiPickersUtilsProvider>
);
};
export default function FormikTextField({ name, ...props }) {
return <Field variant="outlined" name={name} component={CustomField} fullWidth {...props} />;
}
it is very simple just do console.log(formik.values) and you will get all the values without submitting it.

ReactJS - How can I set a value for textfield from material-ui?

I have a selectField and I want to set a value on it. Let say I type on it and when I click a button, the button will call a function that will reset the value of the textfield?
<TextField hintText="Enter Name" floatingLabelText="Client Name" autoWidth={1} ref='name'/>
You can do it in this way
export default class MyCustomeField extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Enter text',
};
}
handleChange = (event) => {
this.setState({
value: event.target.value,
});
};
handleClick = () => {
this.setState({
value:'',
});
};
render() {
return (
<div>
<TextField
value={this.state.value}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>Reset Text</button>
</div>
);
}
}
It's maintained that the right way is to have the component be controlled in a scenario like the accepted answer there, but you can also control the value in this gross and culturally unacceptable way.
<TextField ref='name'/>
this.refs.name.getInputNode().value = 'some value, hooray'
and you can of course retrieve the value like so
this.refs.name.getValue()
Instead of using ref you should use inputRef
const MyComponent = () => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
console.log(input.value);
}}>
<TextField
hintText="Enter Name"
floatingLabelText="Client Name"
autoWidth={1}
inputRef={node => {
input = node;
}}/>
</form>
)
};
Here is a short simple React Hook version of the answer
export default function MyCustomeField({
initialValue= '',
placeholder= 'Enter your text...'
}) {
const [value, setValue] = React.useState(initialValue)
return (
<div>
<TextField
placeholder={placeholder}
value={value}
onChange={e => setValue(e.target.value)}
/>
<button onClick={() => setValue(initialValue)}>Reset Text</button>
</div>
);
}
I would suggest to do a little wrap on the original MUI TextField.
export default function ValueTextField(props) {
const [value, setValue] = useState(props.value);
return (
<TextField {...props} onChange={(event) => setValue(event.target.value)} value={value}/>
);
}
Now you can use your own ValueTextField component now.
<ValueTextField value={"hello world"}></ValueTextField>
I prefer this way to assign the state variables:
<TextField value={mobileNumber}
onChange={e => { this.setState({ mobileNumber: e.target.value }) }}
className={classes.root}
fullWidth={true}
label={t('mobile number')} />

Resources