Mui TextField placeholder is displayed with value on first refresh - reactjs

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".

Related

React useEffect(), fetch data with React Hooks and set the formik form

I want to fetch these (title, description, category) values to the following form. how can I do this?. I'm working on a Formik form that needs to adjust dynamically depending on data. I want to set data to the correct form field when the user going to edit something. I think something missing in my code. any help would be great.
const PostEdit = ({ match, history }) => {
const postId = match.params.id
//Data
const [initialValues, setInitialValues] = useState({
title: '',
description: '',
category: '',
})
const dispatch = useDispatch()
const postDetails = useSelector((state) => state.postDetails)
const { loading, error, post} = postDetails
useEffect(() => {
if (!post.title || post._id !== postId) {
dispatch(listPostDetails(postId))
} else {
setInitialValues({
title: post.title,
description: post.description,
category: post.category
})
}
}, [dispatch, history, postId, post,])
const submitHandler = () => {}
return (
<>
<div>
<Grid>
<Grid item xs={12}>
<Papervariant='outlined'>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={submitHandler}
>
{({ dirty, isValid, values }) => {
return (
<Form>
<DialogContent>
<Grid item container spacing={1} justify='center'>
<Grid item xs={12} sm={12} md={12}>
<Field
label='Title'
name='title'
required
value={values.title}
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={12} md={12}>
<Field
label='Description'
name='description'
value={values.description}
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={12} md={12}>
<Field
label='Category'
name='category'
value={values.category}
component={TextField}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button
disabled={!dirty || !isValid}
variant='contained'
type='Submit'
>
Add
</Button>
</DialogActions>
</Form>
)
}}
</Formik>
</Paper>
</Grid>
</Grid>
</div>
</>
)
}
export default PostEdit
You are missing this prop
enaleReinitialize={true}
So, it should be something like this
<Formik
enableReinitialize={true} // You missed this prop
validateOnChange={true}
initialValues={initialValues}
Formik can be use as hook. https://formik.org/docs/api/useFormik
It'll exposed a function setValues and you can use it to dynamically change the form data

Initializing and setting Material UI TextField state as a number

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

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();
},
});

React re-renders all form controls on every text input change

I am using react with typescript and functional component and Material UI, I have a large form. Small portion is given below.
import React, { useState } from 'react';
import { Grid, TextField } from '#material-ui/core';
const PublicProfileTest = () => {
const [state, setState] = useState<{ town: string; county: string }>({
town: '',
county: '',
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
<>
<Grid container justify='center' alignItems='center' spacing={2}>
<Grid item xs={12} md={6}>
<TextField
variant='outlined'
fullWidth
id='town'
label='Town'
name='town'
autoComplete='town'
placeholder='Town *'
value={state.town || ''}
onChange={handleChange}
/>
</Grid>
</Grid>
<Grid container justify='center' alignItems='center' spacing={2}>
<Grid item xs={12} md={6}>
<TextField
variant='outlined'
fullWidth
id='county'
label='County'
name='county'
autoComplete='county'
placeholder='County'
value={state.county || ''}
onChange={handleChange}
/>
</Grid>
</Grid>
</>
);
};
export default PublicProfileTest;
I have used React dev tools in chrome to check which dome elements are re-rendering.
When i change the town or county input, the whole component gets updated. It's not a problem here but in a large form that i am building, it degrades the performance.
I expect react to re-render only the changed portion of the dom, why it is updating the whole component.
Any idea and solution to make react re-render the changed component only.
Regards,
Iaq

useEffect to trigger address verification based on filled out inputs

I need to run the address verification api call .During these scenarios
*when all associated fields are filled out.
*when above call is done , it should be calling when any of the fields value has
changed.
I tried triggering giving all the fields as dependencies in the useEffects second parameter array,but its calls the effect repeatedly
const Address = props => {
const { countries, usStates, caStates, title, binding, formik } = props;
var zip = formik.values.Client.Address.Residential.Zip;
var city = formik.values.Client.Address.Residential.City;
var line1 = formik.values.Client.Address.Residential.Address1;
var country = formik.values.Client.Address.Residential.Country;
var state = formik.values.Client.Address.Residential.State;
useEffect(() => {
if (zip && city && country && state && country) {
console.log("call address verification")
}
}, [zip, city, country, state, country])
return (
<TransactConsumer>
{({ userSave, getFormApi, formFunction, formStart }) => {
return (
<Fragment>
{title && <Grid item xs={12}>
<Typography variant="body1">{title}</Typography>
</Grid>}
<Grid item xs={12}>
<SectionField
title={title}
name={binding + ".Country"}
required
defaultValue={{ label: "United States", value: "US" }}
label="Country"
suggestions={countries}
component={MuiReactSelect}
/>
</Grid>
<Grid item xs={12}>
<SectionField
title={title}
name={binding + ".Address1"}
required
label="Address Line 1"
fullWidth
component={TextField}
/>
</Grid>
<Grid item xs={12}>
<SectionField
title={title}
name={binding + ".Address2"}
label="Address Line 2"
fullWidth
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={6}>
<SectionField
title={title}
name={binding + ".City"}
required
label="City"
fullWidth
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={4}>
<SectionField
title={title}
name={binding + ".State"}
required
label={isUsCountry() ? "State" : isCaCountry() ? "Province" : "State / Province"}
fullWidth
component={ MuiReactSelect}
/>
</Grid>
<Grid item xs={12} sm={2}>
<SectionField
title={title}
name={binding + ".Zip"}
required
label="Zip"
fullWidth
component={TextField}
/>
</Grid>
</Fragment >
)
}}
</TransactConsumer>
)
}
====SectionField====
import React, { useEffect } from 'react'
import useSectionData from './useSectionData';
import { Field } from 'formik';
import PropTypes from 'prop-types';
const SectionField = ({ children, title, name, ...rest }) => {
const { addField } = useSectionData();
useEffect(() => {
addField(title, name)
}, [title, name])
return (
<Field name={name} {...rest}>
{children}
</Field>
)
}
SectionField.propTypes = {
title: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node),
PropTypes.node]),
};
export default SectionField
Section Field component is wrapper for the formik Field Element.
what would be the best way to make sure I can call only after all the
fields have been filled out . Right now , the it gets called for every
click , like lets say zip is 60000 it calls useEffect 10 times
what can be an other option rather than using formik values to
as dependencies.Any best practices could be helpful. Thanks .
You can have a variable you keep in state that indicates whether all of the fields have been filled out or not. You'd set that variable in the current useEffect that you have. It'd look something like this:
const [ allFieldsFilled, setAllFieldsFilled ] = useState(false);
useEffect(() => {
setAllFieldsFilled(zip && city && country && state && country)
}, [zip, city, country, state, country])
Once you have an indication of whether the fields have all been filled out or not, you could have a second useEffect that'd be responsible for triggering the validation (you could maybe combine them into one, but I think separating them would make the intent a bit clearer):
useEffect(() => {
if(allFieldsFilled){
performValidation();
}
}, [zip, city, country, state, country])
To keep yourself from having to type all the fields you want to be triggering the validation, you could do something like this:
const validationFields = [zip, city, country, state];
useEffect(()=>{
//Code
}, [...validationFields])

Resources