useEffect to trigger address verification based on filled out inputs - reactjs

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])

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

React Hook Form with MUI Autocomplete, FreeSolo and dependent fields

I'm trying to create ZipCode / City / State (in Italian CAP / Città / Provincia) dependent fields from this JSON file (here's the repo as well). I'm using React Hook Form v7 and MUI v5.4.4. I'd like to implement this 3 fields using MUI Autocomplete component with FreeSolo props in order to let the user to insert a custom input value if it's not present in the JSON list.
I tried to make it works but it doesn't. How can I implement that? Furthermore, the validation for the Autocomplete component doesn't work.
Here's the codesandbox that I wrote
There were several problems in your code:
you forget to pass the rules prop to your <Controller />
the current selected value will be passed as the second argument to <Autocomplete />'s onChange handler
you need to use RHF's watch method to react to changes of those 3 dependent fields and filter the options of the other selects accordingly
you need to use flatMap instead of map for the mapping of the options for postal codes, as option.cap is an array
export default function PersonalDetails() {
const { watch } = useFormContext();
const { postalCode, city, state } = watch("personalDetails");
return (
<Card variant="outlined" sx={{ width: 1 }}>
<CardContent>
<Grid container item spacing={2}>
<Grid item xs={12} lg={3}>
<SelectFree
name="personalDetails.postalCode"
label="ZIP (CAP)"
options={options
.filter((option) =>
city || state
? option.nome === city || option.sigla === state
: option
)
.flatMap((option) => option.cap)}
rules={{ required: "Richiesto" }}
/>
</Grid>
<Grid item xs={12} lg={10}>
<SelectFree
name="personalDetails.city"
label="City (Città)"
options={options
.filter((option) =>
postalCode || state
? option.cap.includes(postalCode) || option.sigla === state
: option
)
.map((option) => option.nome)}
rules={{ required: "Richiesto" }}
/>
</Grid>
<Grid item xs={12} lg={2}>
<SelectFree
name="personalDetails.state"
label="State (Sigla)"
options={options
.filter((option) =>
city || postalCode
? option.nome === city || option.cap.includes(postalCode)
: option
)
.map((option) => option.sigla)}
rules={{ required: "Richiesto" }}
/>
</Grid>
</Grid>
</CardContent>
</Card>
);
}
export default function SelectFree({
name,
rules,
options,
getOptionLabel,
...rest
}) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
rules={rules}
defaultValue={null}
render={({
field: { ref, ...field },
fieldState: { error, invalid }
}) => {
return (
<Autocomplete
{...field}
freeSolo
handleHomeEndKeys
options={options}
getOptionLabel={getOptionLabel}
renderInput={(params) => (
<TextField
{...params}
{...rest}
inputRef={ref}
error={invalid}
helperText={error?.message}
/>
)}
onChange={(e, value) => field.onChange(value)}
onInputChange={(_, data) => {
if (data) field.onChange(data);
}}
/>
);
}}
/>
);
}
UPDATE
As you have a very large json file you have two options where you can optimise performance:
limit the amount of options via the filterOptions prop of <Autocomplete /> -> the createFilterOptions function can be configured to set a limit
add a useMemo hook for the filtering and mapping of your options before passing them to the <Autocomplete />, e.g. right now on every input change for the other fields (firstName, lastName, address) the options will be recomputed

How can I get the value of a component I created?

I created a component (it's actually a function but I think they're called components?) for a Select Field.
const useStyles = makeStyles((theme) => ({
formControl: {
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}))
export default function SelectField() {
const classes = useStyles()
const [value, setValue] = React.useState("")
return(
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="age">Age</InputLabel>
<Select
labelId="age"
id="age"
label="Age"
key="index"
value={value}
onChange={(val) => setValue(val)}
>
<MenuItem value=""><em>None</em></MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
)
}
And I'm using this Select field on another "component" (I'll only show the import for that field).
import SelectField from './fields/select'
export default function CreateGame(){
const [questions, setQuestions] = React.useState([{
question: 'asdasdasd',
answer: '',
score: '',
age: ''
}])
const HandleField = (event, index, field) => {
let temp_questions = questions
temp_questions[index][field] = event.target.value
setQuestions(temp_questions)
console.log(questions)
}
const setAge = (e, index) => {
let questions_temp = questions
questions_temp[index]['age'] = e.target.value
setQuestions(questions_temp)
console.log(questions)
}
return(
<Card>
<CardContent>
<Typography>
Add Questions
</Typography>
{questions.map((value, index) =>
<Grid container spacing={1}>
<Grid item lg={2}>
<TextField
value={value['question']}
key={index}
label='Question'
variant='outlined'
onChange={e => HandleField(e, index, 'question')} />
</Grid>
<Grid item lg={2}>
<TextField
value={value['answer']}
key={index}
label='Answer'
variant='outlined'
onChange={e => HandleField(e, index, 'answer')} />
</Grid>
<Grid item lg={2}>
<TextField
value={value['score']}
label='Score'
key={index}
variant='outlined'
onChange={HandleField} />
</Grid>
<Grid item lg={5}>
<SelectField age={value['age']} setAge={setAge} index={index} />
</Grid>
</Grid>
)}
</CardContent>
<Button>Add</Button>
</Card>
)
}
I want to get my <SelectField />'s selected value and save it in my questions state, specifically in the age field. But I don't know exactly how to capture that value. I tried putting a value prop, but I don't think that's the correct way.
Keep the state only in the parent, and pass it down as a prop, as well as another prop - a function which, when called, sets the age in the parent.
You also need to correct the shape of onChange - it accepts an argument of the event, not of the new value.
// in CreateGame
const setAge = i => (newAge) => {
setQuestions([
questions.slice(0, i),
{ ...questions[i], age: newAge },
questions.slice(i + 1),
]);
};
// ...
<Grid item lg={5}>
<SelectField age={questions.age} setAge={setAge(index)} />
</Grid>
export default function SelectField({ age, setAge }) {
// ...
<Select
value={age}
onChange={e => setAge(e.currentTarget.value)}
You can go through these steps
- Step 1 Make an onchange event listener
<SelectField onChange={handleQuestion}/>
- Step 2 Use Hooks to make a state inside functional component
const [Question, setQuestion] = useState("")
- Step 3 Make the method that you had called in step 1
const handleQuestion = () =>{
//First get the value coming fron the onchange listener
console.log(e.target.value);
//Set the state to the question
setQuestion(e.target.value)
}
- Step 4 Pass the value to the question
<TextField
value={Question}//Pass the questions state value here
/>

state values are lately updated - React JS

In my code, I'm trying to set a value to the particular state variables using event method in REACT JS. and also make some calculation for a Discounted price.
After Calculation, set the updated value to the state. But it should be updated lately whenever the next event occurs.
this.state = {
isAddVariant: false,
vendors: [],
variant_price: '',
variant_priceDiscount: '',
variant_priceDiscounted: '',
variant_unit: '',
variants: [],
}
onVariantType = (e) => {
this.setState({
[e.target.name]: e.target.value,
})
if (e.target.name === 'variant_price' || e.target.name === 'variant_priceDiscount') {
let variant_priceDiscounted = this.state.variant_priceDiscount !== '' ? (this.state.variant_price - (this.state.variant_price * (this.state.variant_priceDiscount / 100))).toFixed(2) : this.state.variant_price
this.setState({ variant_priceDiscounted })
}
}
// render function
<Typography variant="h7" color="inherit" style={{ marginTop: 20 }}>
Add Product Variants
</Typography>
<Grid container spacing={24}>
{/* Input - Price */}
<Grid item xs={2}>
<TextField
type={'number'}
name={'variant_price'}
value={nullToEmptyString(variant_price)}
onChange={this.onVariantType}
label={'Price'}
placeholder={'Enter price in ₹'}
required={true}
margin={'dense'}
autoComplete={'off'}
fullWidth
/>
</Grid>
{/* Input - Unit */}
<Grid item xs={2}>
<TextField
name={'variant_unit'}
value={nullToEmptyString(variant_unit)}
onChange={this.onVariantType}
label={'Unit'}
placeholder={'Enter unit (eg: kg)'}
required={true}
margin={'dense'}
autoComplete={'off'}
fullWidth
/>
</Grid>
{/* Input - Discount */}
<Grid item xs={2}>
<TextField
type={'number'}
name={'variant_priceDiscount'}
value={nullToEmptyString(variant_priceDiscount)}
onChange={this.onVariantType}
label={'Discount'}
placeholder={'Enter discount in %'}
margin={'dense'}
autoComplete={'off'}
fullWidth
/>
</Grid>
<Grid item xs={4}>
<TextField
type={'number'}
name={'variant_priceDiscounted'}
value={nullToEmptyString(variant_priceDiscounted)}
label={'Discounted Price'}
margin={'dense'}
autoComplete={'off'}
disabled
fullWidth
/>
</Grid>
<Grid item xs={2}>
{/* Button - Add */}
<IconButton
type={'add'}
aria-label={'Add'}
color={'primary'}
onClick={this.onAddVariant}
>
<IconCheck />
</IconButton>
</Grid>
</Grid>
Expected behavior was Updated Discounted Value is calculated and displayed immediately.
Actual behaviour was the value lately updated Actual behavior image:
setState is async, and take time to update the state. setState takes a callback, you can use it,
onVariantType = (e) => {
let name = e.target.name; //store the name in a variable to use in callback
this.setState({
[e.target.name]: e.target.value,
}, () => {
if (name === 'variant_price' || name === 'variant_priceDiscount') {
let variant_priceDiscounted = this.state.variant_priceDiscount !== '' ? (this.state.variant_price - (this.state.variant_price * (this.state.variant_priceDiscount / 100))).toFixed(2) : this.state.variant_price
this.setState({ variant_priceDiscounted })
}
})
}
Note: We stored e.target.name in a name variable, because after seState execute's e gets nullifies. Read more about Synthetic events.

how to delete the formik values using field array

how can we delete the formik values when using field arrays .So Its gets
deleted from the UI when I delete it , but stays in the formik values.
can I edit/modify formik values directly ?
I am new to react here. Thanks
{
Client:
Phone: [
{
PhoneNumber:"",
PhoneType:""
}
]
}
I am able to delete the occurrence from the state, but formik values still retains the values.
const Phone = ({ title, binding }) => {
const [phones, setPhones] = useState([])
return (
<Fragment>
<Grid item xs={12} md={6}>
<SectionField
title={title}
name={binding + `.Phone.PhoneNumber`}
required
label="Phone"
fullWidth
type="tel"
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={3}>
<SectionField
title={title}
name={binding + `.Phone.PhoneType`}
required
defaultValue={{ label: "", value: "" }}
label="Phone Type"
suggestions={phoneTypes}
component={MuiReactSelect}
/>
</Grid>
<IconButton onClick={() => {
setPhones(currentValue => [...currentValue, {
id: generate(),
PhoneNumber: "",
PhoneType: ""
}])
}}>
<AddBoxIcon fontSize="large" />
</IconButton>
{phones.map((p, index) => (
<Fragment key={p.id}>
<Grid item xs={12} md={5}>
<SectionField
title={title}
name={binding + `.Phone[${index}].PhoneNumber`}
required
label="Phone"
fullWidth
type="tel"
component={TextField}
/>
<ErrorMessage name={`Client.Phone.${index}.PhoneNumber`} /><br />
</Grid>
<Grid item xs={12} sm={3}>
<SectionField
title={title}
name={binding + `.Phone[${index}].PhoneType`}
required
defaultValue={{ label: "", value: "" }}
label="Phone Type"
suggestions={phoneTypes}
component={MuiReactSelect}
/>
</Grid>
<IconButton onClick={() => {
setPhones(currentPhone =>
currentPhone.filter(x => x.id !== p.id))
}}>
<RemoveCircleIcon fontSize="large" />
</IconButton>
</Fragment>
))}
</Fragment>
)
};export default Phone;
Formik values:
======================================================
"Phone": {
"0": {
"PhoneNumber": "8578882942",
"PhoneType": "Home"
},
"PhoneNumber": "8578882942",
"PhoneType": "Home"
},
I got into same sort of inconvenience when i wanted to delete that element from Ui but its values in formik stays there.
What i did was use formik's setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void
So lets say you are using a button to remove that element from UI so i added an extra something to it like this to its onClick property :
//formik.setFieldValue({name of the element that you want to delete})
onClick = {()=>{
//whatever work you need to do for removing it from ui
//then
//formik.setFieldValue({name of the element that you want to delete})
formik.setFieldValue(index)
// here index was the name of variable that i used for that element
}}
Then in the formik's useFormik() hook i did something like this:
const formik = useFormik({
initialValues: {
//your initial values
},
onSubmit: (values) => {
// your onSubmit
},
setFieldValue: (field) => {
delete values.field;
},
});
Sorry i know that this answer is late but i hope it would help someone

Resources