I have a form that has a select list of countries and a cascading select list of regions. When a new country is selected, the corresponding regions are loaded. I am trying to set the value for the region after the the select list loads as the first value in the list. On page render I am loading the country select list here:
useEffect(() => {
fetchCountries();
}, []);
When a new country is selected, I am triggering the corresponding regions to be loaded here:
useEffect(() => {
fetchRegions();
regionData.length && setInput({...input, ["region"]: regionData[0].value})
}, [input["country"]]);
This is also where I am trying to set the default region value as the first item in the list, but the state is not being updated. Where have I gone wrong? More code below, with non-relevant code removed for brevity.
export default function Signup() {
const initialInput: SignupRequest = {
country: "US",
region: ""
};
const initialErrors: ValidationError = {
country: "",
region: ""
};
const [input, setInput] = useState<SignupRequest>(initialInput);
const [errors, setErrors] = useState<ValidationError>(initialErrors);
const [countryData, setCountryData] = useState<SelectListModel["data"]>([]);
const [countryLoaded, setCountryLoaded] = useState<boolean>(false);
const [regionData, setRegionData] = useState<SelectListModel["data"]>([]);
const [regionLoaded, setRegionLoaded] = useState<boolean>(false);
useEffect(() => {
fetchCountries();
}, []);
useEffect(() => {
fetchRegions();
regionData.length && setInput({...input, ["region"]: regionData[0].value})
}, [input["country"]]);
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
const { name, value } = event.target;
setInput({ ...input, [name]: value });
}
function handleBlur(event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
const { name, value } = event.target;
validateInput(name, value);
}
function validateInput(name: string, value: string | string[] | boolean) {
let result = ValidationService.ValidateInput(name, value);
setErrors({ ...errors, [name]: result });
}
function handleFormSubmit(event: React.FormEvent<HTMLFormElement>) {
// process form
}
async function fetchCountries() {
const response = await LocationService.getAllCountries();
if (response?.ok) {
const responseData = await response.json() as CountryResponse[];
const countries = LocationService.maptoCountrySelectList(responseData);
setCountryData(countries);
setCountryLoaded(true);
}
}
async function fetchRegions() {
const response = await LocationService.getRegionsByCountryCode(input["country"]);
if (response?.ok) {
const responseData = await response.json() as RegionResponse[];
const regions = LocationService.maptoRegionSelectList(responseData);
setRegionData(regions);
setRegionLoaded(true);
}
}
return (
<div className="account-content">
<form onSubmit={handleFormSubmit}>
<FormGroup addClass='form-group'>
<Label label='country' title='Country' />
{!countryLoaded
? <LoadingSpinner text="Loading..." />
: <SelectList data={countryData} name='country' value={input["country"]} addClass={countryLoaded && errors["country"] ? 'is-invalid' : ''} onChange={handleSelectChange} onBlur={handleBlur} />}
<FormError message={errors["country"]} />
</FormGroup>
<FormGroup addClass='form-group'>
<Label label='region' title='State/Region/Province' />
{!regionLoaded
? <LoadingSpinner text="Loading..." />
: <SelectList data={regionData} name='region' value={input["region"]} addClass={regionLoaded && errors["region"] ? 'is-invalid' : ''} onChange={handleSelectChange} onBlur={handleBlur} />}
<FormError message={errors["region"]} />
</FormGroup>
<Button type='submit' value='Sign Up' addClass='btn-mdBlue' />
</form>
</div>
)
}
You need to setInput for region inside your fetchRegions function because setState is not synchonous so when you setRegion, and right after you setInput, region is still empty, on next render region gets filled with your previous setRegion but useEffect has already been run
const fetchRegions = async () => {
const regions = await // fetchsomething
setRegions(regions);
setInput({ ...input, region: regions[0].value });
}
Related
I have this code that edit data, it works properly when adding text but removing all letters will not remove first letter.
export const Edit = (props: any) => {
const { initial } = usefetchEditUserType(props)
return (
<>
<CreateUpdate
title="Edit User Type"
useMutationHooks={useEditRole(props.id)} /** Change this */
editPermission={true}
value={initial} -->
id={props.id}
/>
<ListAssignUserAccount {...props} />
</>
);
};
CreateUpdate
const { inputValidation, handleOnChange, handleOnClickSubmit, data } = useMutationHooks;
<CardContent >
<TextField
required
//...omitted code
onChange={(e: any) => handleOnChange(e)}
error={inputValidation['input']}
helperText={inputValidation['input'] ? "Required*" : ""}
value={ data ? data : value } --> value from Edit component
/>
</CardContent>
useEditRole
export const useEditRole = (id: number) => {
const notify = useNotify()
const [inputValidation, setInputValidation] = useState({input: false, button: true})
const [data, setData] = useState("")
const handleOnClickSubmit = async () => {
await fetchCreateRole(data, "PUT", id).then((res: any) => {
notify("Successfully Updated!")
});
}
const handleOnChange = (e: any) => {
if(e.target.value) {
setData(e.target.value )
setInputValidation({ input: false, button: false})
} else {
setInputValidation({ input: true, button: true})
}
}
return { inputValidation, handleOnChange, handleOnClickSubmit, data, setData }
}
console logging seems working but in text field is not getting the changes input status. Im trying way another but then deleting in input field brings back the original or the default value
I'm making a dynamically rendered form using map and I update all the values into a dictionary. However, once I update the values, I storage them into Firebase and when I submit the form, I need to display what I storage as the input default values.
const [local, setLocal] = useState([]);
const [visitante, setVisitante] = useState([]);
const handleChangeLocal = (e, i) => {
const { value, name } = e.target;
const newState = [...local];
newState[i] = {
...newState[i],
[name]: value,
};
setLocal(newState);
};
const handleChangeVisitante = (e, i) => {
const { value, name } = e.target;
const newState = [...visitante];
newState[i] = {
...newState[i],
[name]: value,
};
setVisitante(newState);
};
// FIREBASE
const resultados = {};
for (let i = 1; i < local.length; i++) {
resultados[i] = { local: local[i], visitante: visitante[i] };
}
async function saveResults() {
const decodedToken = await firebase.auth().currentUser?.getIdTokenResult();
const uid = decodedToken?.claims?.user_id;
const db = firebase.firestore();
const quiniela = db
.collection("users")
.doc(uid)
.collection("pool")
.doc("results");
quiniela.set({
resultados: resultados,
});
router.push(`/quiniela/${uid}`);
}
<input
id={i}
type="number"
defaultValue={
userData[partido["partido"]].local[
partido["HomeTeam"]
]
}
className="w-12 border -ml-8 text-center"
placeholder="#"
name={partido["HomeTeam"]}
required
step={1}
min={0}
max={20}
onChange={(e) =>
handleChangeLocal(e, partido["partido"])
}
/>
<input
id={i}
type="number"
className="w-12 border ml-20 text-center"
placeholder="#"
defaultValue={
userData[partido["partido"]].visitante[
partido["AwayTeam"]
]
}
name={partido["AwayTeam"]}
step={1}
min={0}
max={20}
onChange={(e) =>
handleChangeVisitante(e, partido["partido"])
}
required
/>
It works when I submit data for the first time, however, onChange doesn't work once I set the defaultValues from Firebase.
What's going on? Or what should I change?
Because defaultValue cannot be updated.
Use value instead
value={
userData[partido["partido"]].local[
partido["HomeTeam"]
]
}
const [name, setName] = useState("");
const [age, setAge] = useState("");
const initialValues = {
name: "",
age: "",
};
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
setIsSubmit(true);
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name is required";
}
if (!values.age) {
errors.age= "Age is required";
}
return errors;
};
const userCreate = async () => {
await api.post("/createuser", {
name,
age,
});
};
return (
<div class="container">
<Form
onSubmit={
Object.keys(formErrors).length === 0 && isSubmit
? userCreate
: handleSubmit
}
>
<Form.Field>
<label>Name</label>
<input
name="name"
onChange={(e) => {
setName(e.target.value);
handleChange(e);
}}
values={formValues.name}
/>
<span className="error-message">{formErrors.name}</span>
</Form.Field>
<Form.Field>
<label>Age</label>
<input
name="age"
onChange={(e) => {
setAge(e.target.value);
handleChange(e);
}}
values={formValues.age}
/>
<p className="error-message">{formErrors.age}</p>
</Form.Field>
<Button type="submit">Submit</Button>
</Form>
</div>
);
I'm trying to use axios to do POST method for creating user.
I got everything works fine but there's one small problem but I don't know how to fix.
The problem is that I always need to submit the form 2 times to make the POST request. There's nothing happen in the first submit, but it will work in the second submit.
Does anyone know what's wrong with my code?
Edited
According to #DBS solution.
I'm trying to follow the steps but now the form can't submit anymore. Can someone let me know if I missed something?
const [name, setName] = useState("");
const [age, setAge] = useState("");
const initialValues = {
name: "",
age: "",
};
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
if (!Object.keys(formErrors).length && !isSubmitting) {
setFormErrors(validate(formValues));
} else {
userCreate();
setisSubmitting(true);
}
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name is required";
}
if (!values.age) {
errors.age= "Age is required";
}
return errors;
};
const userCreate = async () => {
await api.post("/createuser", {
name,
age,
});
};
return (
<div class="container">
<Form
onSubmit={handleSubmit}
>
<Form.Field>
<label>Name</label>
<input
name="name"
onChange={(e) => {
setName(e.target.value);
handleChange(e);
}}
values={formValues.name}
/>
<span className="error-message">{formErrors.name}</span>
</Form.Field>
<Form.Field>
<label>Age</label>
<input
name="age"
onChange={(e) => {
setAge(e.target.value);
handleChange(e);
}}
values={formValues.age}
/>
<p className="error-message">{formErrors.age}</p>
<
The issue here is your isSubmit, it is required to be true for userCreate to be called:
onSubmit={
Object.keys(formErrors).length === 0 && isSubmit
? userCreate
: handleSubmit
}
But it starts as false:
const [isSubmit, setIsSubmit] = useState(false);
And is only ever updated when handleSubmit is called (which, confusingly, is only called when the validation fails)
So your current code does this:
isSubmit is false
Submit is clicked, handleSubmit is called and isSubmit is set to true
Submit is clicked again, now isSubmit is true it will call userCreate
To solve this, there are a few different approaches, but I would:
Move all the submit handler logic into onSubmit={handleSubmit} (To keep things clear)
Inside there, do your error length check (0 error) and isSubmit (Which I would probably rename to isSubmitting for clarity, and make sure it's false) (E.g. !Object.keys(formErrors).length && !isSubmitting)
If there are errors, show the appropriate message (Leaving isSubmitting as false)
If not, call userCreate (And set isSubmitting to true)
Lastly, if this can be submitted multiple times, add an effect/callback/then to reset isSubmitting once the call is complete.
Are you using the isSubmitting flag for something? if not below might be work for you.
If there is no error, calling the create method
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
if(Object.keys(formErrors).length === 0) {
userCreate();
}
};
if isSubmitting is used to check the submit or create in progress
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
if(Object.keys(formErrors).length === 0) {
setIsSubmitting(true);
userCreate();
}
};
The flag isSubmitting should be set as false on API is success or failed
setIsSubmitting(false)
I am pretty new to React, and the only CRUD functionality I have done has been in MVC.net, so I am pretty lost here and none of the tutorials I am finding seem to be the situation I have going on...or maybe I am just misunderstanding them. The tips I got with my initial question helped, but I am still no getting it to work, so hopefully now I have supplies enough info to help other help me. I did no include all of the input fields because their are like 15 and it was just redundant.
I am pulling up the modal using this onClick event:
onClick={()=> {handleEditModal(item)}}
modalFunctions.js
// Modal Functionality
export function ModalFunctions() {
const [selectedRecord, setSelectedRecord] = useState([]);
const [openModal, setOpenModal] = useState(false);
const handleEditModal = item =>{
setOpenModal(true)
setSelectedRecord(item)
}
return {
selectedRecord,
setSelectedRecord,
openModal,
setOpenModal,
handleEditModal,
handleDetailModal
}
}
// Form Functionality
export function FormFunctions(validateOnChange = false, validate) {
const [values, setValues] = useState('');
const [errors, setErrors] = useState({});
const handleInputChange = e => {
const { name, value } = e.target
setValues({
...values,
[name]: value
})
if (validateOnChange)
validate({ [name]: value })
}
return {errors, handleInputChange, setErrors, setValues, values}
}
DataTable.js
// Imported Modal Functions
const {
selectedRecord,
openModal,
setOpenModal,
handleEditModal,
handleDetailModal
} = ModalFunctions();
const baseURL = 'http://localhost:8080/api/tanks';
// Fetch Data
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get(baseURL);
setTableData(response);
} catch (error) {
console.error(error.message);
}
setLoading(false);
};
fetchData();
}, [baseURL, setTableData, setLoading]);
// The Modal
return(
<Modal
title={ "Editing: " + (selectedRecord.tankName) }
openModal={openModal}
setOpenModal={setOpenModal}
>
<TankEdit
selectedRecord={selectedRecord}
setOpenModal={setOpenModal}
openModal={openModal}
/>
</Modal>
)
TankEdit.js
export function TankEdit(props) {
const { baseURL, openModal, selectedRecord, setOpenModal, setTableData } = props;
const validate = (fieldValues = item) => {
let temp = { ...errors }
if ('tankName' in fieldValues)
temp.tankName = fieldValues.tankName ? "" : "This field is required."
setErrors({
...temp
})
if (fieldValues === values)
return Object.values(temp).every(x => x === " ")
}
const {
values,
setValues,
errors,
setErrors,
handleInputChange,
} = FormFunctions(true, validate);
useEffect(() => {
if (selectedRecord !== null)
setValues({
...selectedRecord
})
}, [selectedRecord, setValues])
function editRecord() {
axios.put(`${baseURL}`, {
title: "Success",
body: "The record had been successfully updated"
}).then ((response) => {setTableData(response.data);})
}
const handleSubmit = e => {
e.preventDefault()
if (validate()) {
editRecord(values);
}
setOpenModal(false)
}
const item = values; // used for easier referencing (matches table item)
return (
<Form onSubmit={handleSubmit} open={openModal}>
<Grid>
<Controls.Input
name="tankName"
label="Tank Name"
value={item.tankName}
onChange={handleInputChange}
error={errors.tankName}
/>
</Grid>
<Grid>
<Controls.Button
type="submit"
text="Submit"
/>
<Controls.Button
text="Cancel"
color="default"
onClick={()=>{setOpenModal(false)}}
/>
</Grid>
</Form>
)
}
Input.js
export default function Input(props) {
const { error=null, label, name, onChange, value, ...other } = props;
return (
<TextField
variant="outlined"
label={label}
name={name}
value={value}
defaultValue=''
onChange={onChange}
{...other}
{...(error && {error:true,helperText:error})}
/>
)
}
My company is only wanting a Read and an Update function, since Creating and Deletion will be handled another ways, so this is my final hangup. I think I am close, but I am missing something.
Can anyone point me in the right direction?
THANKS!!!!
If you want to write an update request you would use axios.put to send the data to your back-end.
In your handleSubmit function you do:
let response = await axios.put('http://-------/api/tanks', { name: 'tank' });
(The second parameter is an object that needs to contain all the form data fields)
Also make sure you call e.preventDefault() in the handleSubmit function so you don't accidentally navigate elsewhere.
Then you will update your database or whatever using your back-end.
for update you should use put or patch method
and you should send id of item you want to update in request url.
I insert 2 example here.
this is for put:
const res = await axios.put('/api/article/123', {
title: 'Making PUT Requests with Axios',
status: 'published'
});
this is for patch:
const res = await axios.patch('/api/article/123', {
title: 'Making PUT Requests with Axios',
status: 'published'
});
How can I retrieve the dishId selected from my options react - select that shows me thedishType in order to send them my parent component FormRender and to the backend.
My first dropdown shows me: Menu1, Menu2...
My second one: type2...
So if I click ontype4, how can I store the related dishId(here = 4). I can click on several values i.e: type2 andtype3.
How do I keep the dish ids i.e : 2 and 3 and send them to my FormRender parent
Menus(first page of my multi - step form):
export default function Menus() {
const [selectionMenus, setSelectionMenus] = useState({});
const [selectionDishes, setSelectionDishes] = useState({});
const [menus, setMenus] = useState([])
const [date, setDate] = useState('')
useEffect(() => {
axios
.post(url)
.then((res) => {
console.log(res);
setMenus(res.data.menus);
})
.catch((err) => {
console.log(err);
});
}, []);
const names = menus?.map(item => {
return {
label: item.menuId,
value: item.name
}
})
const types = menus?.flatMap(item => {
return item.dishes.map(d => ({
label: d.dishId,
value: d.dishType
}))
})
const handle = (e) => {
if (e?.target?.id === undefined) return setInfo(e);
if (e?.target?.id === undefined) return setSelectionMenus(e);
if (e?.target?.id === undefined) return setSelectionDishes(e);
switch (e.target.id) {
case "date":
setDate(e.target.value);
break;
...
default:
}
}
};
return (
<>
<form>
<div>My menus</div>
<label>
Dishes :
<Dropdown
options={names}
value={selectionMenus}
setValue={setSelectionMenus}
isMulti={true}
/>
</label>
<label>
<Dropdown
options={types}
value={selectionDishes}
setValue={setSelectionDishes}
isMulti={true}
/>
</label>
<label>
Date:
<div>
<input
type="date"
name='date'
value={date}
onChange={handle}
id="date"
/>
</div>
</label>
...
</form>
<div>
<button onClick={() => nextPage({ selectionDishes, selectionMenus, date })}>Next</button>
</div>
</>
);
}
Here the parent Component FormRender that is supposed to retrieve the values of all dishId selected and send them to the backend:
export default function FormRender() {
const [currentStep, setCurrentStep] = useState(0);
const [info, setInfo] = useState();
const [user, setUser] = useState();
const headers = ["Menus", "Details", "Final"];
const steps = [
<Menus
nextPage={(menu) => {
setInfo(menu);
setCurrentStep((s) => s + 1);
}}
/>,
<Details
backPage={() => setCurrentStep((s) => s - 1)}
nextPage={setUser}
/>,
<Final />
];
useEffect(() => {
if (info === undefined || user === undefined) return;
const data = {
date: info.date,
id: //list of dishId selected but don't know how to do that??
};
}, [info, user]);
return (
<div>
<div>
<Stepper steps={headers} currentStep={currentStep} />
<div >{steps[currentStep]}</div>
</div>
</div>
);
}
Dropdown:
export default function Dropdown({ value, setValue, style, options, styleSelect, isMulti = false }) {
function change(option) {
setValue(option.value);
}
return (
<div onClick={(e) => e.preventDefault()}>
{value && isMulti === false ? (
<Tag
selected={value}
setSelected={setValue}
styleSelect={styleSelect}
/>
) : (
<Select
value={value}
onChange={change}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
Here my json from my api:
{
"menus": [
{
"menuId": 1,
"name": "Menu1",
"Description": "Descritption1",
"dishes": [
{
"dishId": 2,
"dishType": "type2"
},
{
"dishId": 3,
"dishType": "type3"
},
{
"dishId": 4,
"dishType": "type4"
}
]
},
...
]
}
You already store the selected values inside the selectionMenus and selectionDishes states. So, if you want to send them to the parent FormRender component you can instead create those two states inside that component like this:
export default function FormRender() {
const [selectionMenus, setSelectionMenus] = useState();
const [selectionDishes, setSelectionDishes] = useState();
....
}
Then pass those values to the Menus component:
<Menus
selectionMenus={selectionMenus}
setSelectionMenus={setSelectionMenus}
selectionDishes={selectionDishes}
setSelectionDishes={setSelectionDishes}
nextPage={(menu) => {
setInfo(menu);
setCurrentStep((s) => s + 1);
}}
/>
Subsequently, you will have to remove the state from the Menus component and use the one you receive from props:
export default function Menus({ selectionMenus, setSelectionMenus, selectionDishes, setSelectionDishes }) {
/*const [selectionMenus, setSelectionMenus] = useState({}); //remove this line
const [selectionDishes, setSelectionDishes] = useState({});*/ //remove this line
...
}
Finally, you can use inside your useEffect hook the two states and map them to only get the selected ids:
useEffect(() => {
// ... other logic you had
if(selectionDishes?.length && selectionMenus?.length){
const data = {
date: info.date,
id: selectionDishes.map(d => d.dishId),
idMenus: selectionMenus.map(m => m.menuId)
};
}
}, [info, user, selectionMenus, selectionDishes]);
react-select has options to format the component:
getOptionLabel: option => string => used to format the label or how to present the options in the UI,
getOptionValue: option => any => used to tell the component what's the actual value of each option, here you can return just the id
isOptionSelected: option => boolean => used to know what option is currently selected
onChange: option => void => do whatever you want after the input state has changed
value => any => if you customize the above functions you may want to handle manually the value
Hope it helps you