Initializing and setting Material UI TextField state as a number - reactjs

I am working on a workout logger, where users can log their sets for exercises. I am using the MERNG stack for this project. Currently, I am working on the validation for logging a set on the front end. This will ask users for these: exercise name(String), weight(Float), reps(Int) and notes(String - optional) in a form. The problem I am having, is initializing and setting the weight and reps in their respected types. This is the initial states I have set for these fields:
{
exerciseName: "",
weight: "",
reps: "",
notes: "",
}
I realize that this will return the weight and reps as Strings, so I tried this as well (although, I don't want fields to have a weight and rep already entered for them, in case they forget to input these themselves and add it to their logs).
{
exerciseName: "",
weight: 0,
reps: 0,
notes: "",
}
This approach works to a certain degree, as the console logs them as Numbers but when the user changes this in the TextField(Material UI), they end up getting submitted as Strings anyway. Therefore, I receive the 400 status code from my backend, as these should be a Float and an Int. How can I achieve the result of initializing and setting these values as numbers only, so no error fires?
Form
const [errors, setErrors] = useState({});
const { onChange, onSubmit, values } = useForm(registerSet, { // Uses a hook for forms
exerciseName: "",
weight: "",
reps: "",
notes: "",
});
return (
<form
onSubmit={onSubmit}
id="addSetForm"
noValidate
autoComplete="off"
>
<TextField
name="exerciseName"
label="Exercise Name"
variant="outlined"
fullWidth
select
className={classes.formInput}
value={values.exerciseName}
error={errors.exerciseName ? true : false}
onChange={onChange}
>
<MenuItem key="Bench Press" value="Bench Press">
Bench Press
</MenuItem>
<MenuItem key="Deadlift" value="Deadlift">
Deadlift
</MenuItem>
<MenuItem key="Squat" value="Squat">
Squat
</MenuItem>
</TextField>
<Grid container spacing={1} className={classes.formInput}>
<Grid item xs={6}>
<TextField
name="weight"
label="Weight"
type="number"
variant="outlined"
fullWidth
value={values.weight}
error={errors.weight ? true : false}
onChange={onChange}
/>
</Grid>
<Grid item xs={6}>
<TextField
name="reps"
label="Reps"
type="number"
variant="outlined"
fullWidth
value={values.reps}
error={errors.reps ? true : false}
onChange={onChange}
/>
</Grid>
</Grid>
</form>
)
formHooks.js (useForm)
export const useForm = (callback, initialState = {}) => {
const [values, setValues] = useState(initialState);
const onChange = (event) => {
setValues({ ...values, [event.target.name]: event.target.value });
};
const onSubmit = (event) => {
event.preventDefault();
callback();
};
return {
onChange,
onSubmit,
values
};
};

You need to alter onChange function. If you console.log(typeof event.target.value) it will be string.
const onChange = (event) => {
setValues({ ...values, [event.target.name]: e.target.type === 'number' ? parseInt(e.target.value) : e.target.value });
};

Related

Mui TextField placeholder is displayed with value on first refresh

I'm getting this weird behavior that I don't know how to solve, on edit mode of this form if I refresh the page I get a bug where both the value and the placeholder are displayed in the field
-- This is my form component
const Form = () => {
// fetch hook to get the settings data.
const settings = useGetSettingsQuery();
// initialize the useFormik hook with the data just fetched
const form = useSettingsForm({ initialValues: settings.data?.data ?? {} });
return (
<Box>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
fullWidth
name={'id'}
label={'id'}
placeholder={'ticket id'}
variant="outlined"
value={form.values.id}
onChange={form.handleChange}
/>
</Grid>
<Grid item xs={12}>
initial values
<pre>{JSON.stringify({ id: form.initialValues.id }, null, 2)}</pre>
</Grid>
<Grid item xs={12}>
current value values
<pre>{JSON.stringify({ id: form.values.id }, null, 2)}</pre>
</Grid>
</Grid>
</Box>
);
};
-- and this is my hook, right now I've deleted everything in my hook, and this is what's left:
export const useSettingsForm = ({ initialValues }: Props) => {
return useFormik<Partial<ISetting>>({
enableReinitialize: true,
initialValues: {
...initialValues,
},
validationSchema: Yup.object().shape({}),
onSubmit: async (values) => {
console.log('submitted -> ', values);
},
});
};
the current behavior
For my useGetSettings hook, I'm using RTK query to fetch the data and handle the server state, this is the a snippet of apiSlice:
export const settingApiSlice = apiSlice.injectEndpoints({
endpoints(builder) {
return {
getSettings: builder.query<IGetSettingsResp, void>({
query() {
return `/setting`;
},
providesTags: ['setting'],
}),
};
},
});
export const { useGetSettingsQuery } = settingApiSlice;
as you can see in the picture the placeholder text and value are displayed, is there any way to fix this bug, thank you
In Formik, the name of the input ties into the property of it's value inside of form.values. So this:
<TextField
fullWidth
name={'ticket number'}
label={'ticket number'}
placeholder={'ticket number'}
variant="outlined"
value={form.values.id}
onChange={form.handleChange}
/>
Should be this:
<TextField
fullWidth
name="id"
label={'ticket number'}
placeholder={'ticket number'}
variant="outlined"
value={form.values.id}
onChange={form.handleChange}
/>
When you use name={'ticket number'} (or name="ticket number"), it's literally trying to set the value on form.values.ticket number instead of form.values.id, as you want it to be since that's your value.
The id in value={form.values.id} is connected to name="id".

This is first time I'm coming across this onBlur event how can i do it?

I will try to explain best as I can. Here what I want is whenever I enter email in my mail field I want another input field to show just below email section. That is not difficult part difficult part is it should be onBlur event whenever i have entered email after loosing focus it should show another textfield or input field just below that I will show you the picture here is the pic
And here is the code
<Typography
color="#05445E"
fontFamily="'Jost', sans-serif"
fontSize={15}
>
Email
</Typography>
<Input
fullWidth
name="email"
value={user.email}
onChange={handleChange}
disableUnderline={true}
className={classes.inputEmail}
endAdornment={
<>
{user.clearEmail ? (
<IconButton
onClick={() => clearValue("email", "clearEmail")}
>
<ClearIcon />
</IconButton>
) : (
""
)}
</>
}
/>
and here is the state i have used..
const [user, update_user] = useState({
user_name: "",
email: "",
clearUser: false,
clearEmail: false,
});
const handleChange = (event) => {
const { name: key, value } = event.target;
if (value) {
if (key === "user_name")
update_user({ ...user, [key]: value, clearUser: true });
else if (key === "email")
update_user({ ...user, [key]: value, clearEmail: true });
} else
update_user({ ...user, [key]: "", clearUser: false, clearEmail: false });
};
This is first time come across this task it will be great help if anyone can help me with example thank you..

Formik. Dirty check

Can someone tell me how to make the addition of data work together with the check through dirty. The rendering of the button works, but when I click on it the initialState gets new data and is updated, hence the dirty should return to false, but it is not.
state:
const [store, setStore] = useState<UserDataType>({
firstName: 'Artem',
lastName: 'Bugay',
email: '',
age: '',
country: '',
region: '',
placeOfWork: '',
occupation: '',
});
Function what save changes to local store:
const changeState = (values: UserDataType) => {
setStore(values);
};
Component return:
return (
<Styled.WrapperContainer>
<Styled.Container>
<GlobalStyled.Title>Your profile</GlobalStyled.Title>
<Formik initialValues={store} onSubmit={updateProfile}>
{({ values, isSubmitting, handleChange, dirty }) => {
return (
<GlobalStyled.FormFormik>
{console.log('dirty', dirty)}
<Styled.Field
type="text"
name="firstName"
label="First Name"
value={values?.firstName}
onChange={handleChange}
/>
<Styled.Field
type="text"
name="lastName"
label="Last Name"
value={values?.lastName}
onChange={handleChange}
/>
<Styled.Field
type="email"
name="email"
label="Email"
value={values?.email}
onChange={handleChange}
/>
...
<Styled.ButtonAntd
data-testid="profile"
htmlType="submit"
disabled={!dirty}
onClick={() => changeState(values)}
>
Update
{/* <Spinner loading={isLoading === StateStatus.Pending} size={20} /> */}
</Styled.ButtonAntd>
</GlobalStyled.FormFormik>
);
}}
</Formik>
</Styled.Container>
</Styled.WrapperContainer>
);
onSubmit={(values, { setSubmitting, resetForm }) => {
setSubmitting(true);
setTimeout(async () => {
resetForm({ values });
}, 100);
}}
You don't need to use setStore, you have variable {values}.
Every time field value changes, you have updated data in {values}. You can check with your custom function onChange element you create. Check this info too.
Also, you can check fields data with advanced lib.

Resetting form fields to initial state in React

I'm currently working on a form (Material-UI) that users fill in to log a set they did for an exercise. Upon submission, it will run a GraphQL mutation. As I also have login and register functionalities that share similarities, I created a form hook for these. The login and register do not need to be reset, as this is done by redirecting them to the home page. However, for the logging set functionality, I want the modal (where the form is) to close after resetting the form back to it's initial state, so that when they choose to log another set, the form does not contain the values from the previous logged set.
Form
const initialState = {
exerciseName: "",
weight: undefined,
reps: undefined,
notes: "",
};
function MyModal() {
const [errors, setErrors] = useState({});
const { onChange, onSubmit, values } = useForm(registerSet, initialState);
const [addSet] = useMutation(ADD_SET, {
update() {
// need to reset form to initial state here
handleClose();
},
onError(err) {
setErrors(err.graphQLErrors[0].extensions.exception.errors);
},
variables: values,
});
function registerSet() {
addSet();
}
return (
<form
onSubmit={onSubmit}
id="addSetForm"
noValidate
autoComplete="off"
>
<TextField
name="exerciseName"
label="Exercise Name"
select
value={values.exerciseName}
error={errors.exerciseName ? true : false}
onChange={onChange}
>
<MenuItem key="Bench Press" value="Bench Press">
Bench Press
</MenuItem>
<MenuItem key="Deadlift" value="Deadlift">
Deadlift
</MenuItem>
<MenuItem key="Squat" value="Squat">
Squat
</MenuItem>
</TextField>
<Grid container spacing={1}>
<Grid item xs={6}>
<TextField
name="weight"
label="Weight"
type="number"
value={values.weight}
error={errors.weight ? true : false}
onChange={onChange}
/>
</Grid>
<Grid item xs={6}>
<TextField
name="reps"
label="Reps"
type="number"
value={values.reps}
error={errors.reps ? true : false}
onChange={onChange}
/>
</Grid>
</Grid>
<TextField
name="notes"
label="Notes (Optional)"
type="text"
multiline={true}
rows="4"
value={values.notes}
onChange={onChange}
/>
</form>
)
}
useForm Hook
export const useForm = (callback, initialState = {}) => {
const [values, setValues] = useState(initialState);
const onChange = (event) => {
setValues({
...values,
[event.target.name]:
event.target.type === "number"
? parseInt(event.target.value)
: event.target.value,
});
};
const onSubmit = (event) => {
event.preventDefault();
callback();
};
return {
onChange,
onSubmit,
passwordVisibility,
confirmPasswordVisibility,
values,
};
};
I'm not sure how I can access setValues from the useForm hook in the update() handler for useMutation to reset the form back to it's initial state.
Step 1: create a resetValues() function inside your useForm hook and export it
const resetValues = () => {
setValues(initialState)
};
return {
// ... OTHER EXPORTS
resetValues,
};
Step 2: Then use this function inside your component
const { onChange, onSubmit, resetValues, values } = useForm(registerSet, initialState);
const [addSet] = useMutation(ADD_SET, {
update() {
resetValues(); // SEE HERE
handleClose();
},
});

Downshift autocomplete onBlur resetting value with Formik

I have a form with a field that needs to show suggestions via an api call. The user should be allowed to select one of those options or not and that value that they type in gets used to submit with the form, but this field is required. I am using Formik to handle the form, Yup for form validation to check if this field is required, downshift for the autocomplete, and Material-UI for the field.
The issue comes in when a user decides not to use one of the suggested options and the onBlur triggers. The onBlur always resets the field and I believe this is Downshift causing this behavior but the solutions to this problem suggest controlling the state of Downshift and when I try that it doesn't work well with Formik and Yup and there are some issues that I can't really understand since these components control the inputValue of this field.
Heres what I have so far:
const AddBuildingForm = props => {
const [suggestions, setSuggestions] = useState([]);
const { values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
modalLoading,
setFieldValue,
setFieldTouched,
classes } = props;
const loadOptions = (inputValue) => {
if(inputValue && inputValue.length >= 3 && inputValue.trim() !== "")
{
console.log('send api request', inputValue)
LocationsAPI.autoComplete(inputValue).then(response => {
let options = response.data.map(erlTO => {
return {
label: erlTO.address.normalizedAddress,
value: erlTO.address.normalizedAddress
}
});
setSuggestions(options);
});
}
setFieldValue('address', inputValue); // update formik address value
}
const handleSelectChange = (selectedItem) => {
setFieldValue('address', selectedItem.value); // update formik address value
}
const handleOnBlur = (e) => {
e.preventDefault();
setFieldValue('address', e.target.value);
setFieldTouched('address', true, true);
}
const handleStateChange = changes => {
if (changes.hasOwnProperty('selectedItem')) {
setFieldValue('address', changes.selectedItem)
} else if (changes.hasOwnProperty('inputValue')) {
setFieldValue('address', changes.inputValue);
}
}
return (
<form onSubmit={handleSubmit} autoComplete="off">
{modalLoading && <LinearProgress/>}
<TextField
id="name"
label="*Name"
margin="normal"
name="name"
type="name"
onChange={handleChange}
value={values.name}
onBlur={handleBlur}
disabled={modalLoading}
fullWidth={true}
error={touched.name && Boolean(errors.name)}
helperText={touched.name ? errors.name : ""}/>
<br/>
<Downshift id="address-autocomplete"
onInputValueChange={loadOptions}
onChange={handleSelectChange}
itemToString={item => item ? item.value : '' }
onStateChange={handleStateChange}
>
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
}) => (
<div>
<TextField
id="address"
label="*Address"
name="address"
type="address"
className={classes.autoCompleteOptions}
{...getInputProps( {onBlur: handleOnBlur})}
disabled={modalLoading}
error={touched.address && Boolean(errors.address)}
helperText={touched.address ? errors.address : ""}/>
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{suggestions.map((suggestion, index) =>
<MenuItem {...getItemProps({item:suggestion, index, key:suggestion.label})} component="div" >
{suggestion.value}
</MenuItem>
)}
</Paper>
) : null}
</div>
</div>
)}
</Downshift>
<Grid container direction="column" justify="center" alignItems="center">
<Button id="saveBtn"
type="submit"
disabled={modalLoading}
className = {classes.btn}
color="primary"
variant="contained">Save</Button>
</Grid>
</form>
);
}
const AddBuildingModal = props => {
const { modalLoading, classes, autoComplete, autoCompleteOptions } = props;
return(
<Formik
initialValues={{
name: '',
address: '',
}}
validationSchema={validationSchema}
onSubmit = {
(values) => {
values.parentId = props.companyId;
props.submitAddBuildingForm(values);
}
}
render={formikProps => <AddBuildingForm
autoCompleteOptions={autoCompleteOptions}
autoComplete={autoComplete}
classes={classes}
modalLoading={modalLoading}
{...formikProps} />}
/>
);
}
Got it to work. Needed to use handleOuterClick and set the Downshift state with the Formik value:
const handleOuterClick = (state) => {
// Set downshift state to the updated formik value from handleOnBlur
state.setState({
inputValue: values.address
})
}
Now the value stays in the input field whenever I click out.

Resources