React Form Validation Issue - reactjs

i don't know what is wrong with my code , i check the value with console.log and it gets the value normally but when i submit the form it gets back to undefined , and when i try to check the form with if statement if just send the mail with empty values and sometimes it send undefined
const [mails, setMails] = useState({
userFullName: '',
userEmail: '',
userSubject: '',
userMessage: '',
mailFail : false,
});
const [mailStatues, setMailStatues] = useState(false);
const [mailLoader, setMailloader] = useState(false);
const { userFullName, userEmail, userSubject, userMessage, mailFail } = mails;
const handleChange = e => {
setMails({ ...mails, [e.target.name] : e.target.value})
}
function setFail() {
setMails({ mailFail: true })
setTimeout(() => {
setMails({ mailFail: false })
}, 3000);
}
const handleEmail = async (e) => {
e.preventDefault();
console.log(userFullName , userEmail, userSubject, userMessage)
if ( userFullName ) {
setFail()
}
else {
setMailloader(true);
await axios.post('http://localhost:8000/api/form', {
...mails
},
setMails({
userFullName: '',
userEmail: '',
userSubject: '',
userMessage: '',
})
,
setMailloader(false)
,
setMailStatues(true)
)
}
}
here is the form
<form onSubmit={handleEmail}>
<Input id='inputName' type="text" name="userFullName" label="Name" value={userFullName} change={handleChange} />
<Input id='inputEmail' type="email" name="userEmail" label="Email" value={userEmail} change={handleChange}/>
<Input id='inputSubject' type="text" name="userSubject" label="Subject" value={userSubject} change={handleChange}/>
<TextArea id='inputMessage' type="text" name="userMessage" label="Message" value={userMessage} change={handleChange}/>
<BtnAni string="Submit" loading={mailLoader}/>
</form>

Change these lines
setMails({ mailFail: true })
setTimeout(() => {
setMails({ mailFail: false })
}, 3000);
to
setMails(prev => ({ ...prev, mailFail: true }))
setTimeout(() => {
setMails(prev => ({ ...prev, mailFail: false }))
}, 3000);

Related

form error, 'password is required' response from the backend

this is my scheduleForm component
import { LoginOrSchedule } from 'context/isLoginOrIsSquedule/Context';
import React, { useContext, useState } from 'react';
import * as Styled from './styles';
export type ScheduleFormProps = {
errorMessage?: string;
onScheduleSend?: (
name: string,
email: string,
password: string,
age: number,
) => void;
};
export const ScheduleForm = ({
errorMessage,
onScheduleSend,
}: ScheduleFormProps) => {
const [loading, setLoading] = useState(false);
const [form, setForm] = useState({
name: '',
email: '',
password: '',
confirmPass: '',
age: 0,
});
const { dispatch } = useContext(LoginOrSchedule);
const handleSubmitForm = async (e: React.FormEvent) => {
e.preventDefault();
const { age, confirmPass, email, name, password } = form;
if (!age || !confirmPass || !email || !name || !password) {
alert('Um ou mais campos estão em branco');
return;
}
if (confirmPass !== password) {
alert('As senhas não batem');
return;
}
setLoading(true);
if (onScheduleSend) {
await onScheduleSend(name, email, password, age); //<<<sending to backend
}
setLoading(false);
setForm({
name: '',
email: '',
password: '',
confirmPass: '',
age: 0,
});
};
const handleLogin = () => {
dispatch({ type: 'TRIGGER' });
};
return (
<Styled.Wrapper>
<h2>Amostra de formulário de Cadastro</h2>
<Styled.Form onSubmit={handleSubmitForm}>
{!!errorMessage && (
<Styled.ErrorMessage>{errorMessage}</Styled.ErrorMessage>
)}
<Styled.InputWrapper>
<Styled.InputText
id="user-name"
type="text"
name="user-name"
onChange={(e) => {
setForm((f) => {
return { ...f, name: e.target.value };
});
}}
value={form.name}
/>
<Styled.InputLabel htmlFor="user-name">Nome</Styled.InputLabel>
</Styled.InputWrapper>
<Styled.InputWrapper>
<Styled.InputText
id="user-email"
type="email"
name="user-email"
onChange={(e) => {
setForm((f) => {
return { ...f, email: e.target.value };
});
}}
value={form.email}
/>
<Styled.InputLabel htmlFor="user-email">Email</Styled.InputLabel>
</Styled.InputWrapper>
<Styled.InputWrapper>
<Styled.InputText
id="user-age"
type="number"
name="user-age"
onChange={(e) => {
setForm((f) => {
return { ...f, age: Number(e.target.value) };
});
}}
value={form.age === 0 ? '' : form.age}
/>
<Styled.InputLabel htmlFor="user-age">Idade</Styled.InputLabel>
</Styled.InputWrapper>
<Styled.InputWrapper>
<Styled.InputText
id="user-pássword"
type="password"
name="user-password"
onChange={(e) => {
setForm((f) => {
return { ...f, password: e.target.value };
});
}}
value={form.password}
/>
<Styled.InputLabel htmlFor="user-password">Senha</Styled.InputLabel>
</Styled.InputWrapper>
<Styled.InputWrapper>
<Styled.InputText
id="user-confirmPass"
type="password"
name="user-confirmPass"
onChange={(e) => {
setForm((f) => {
return { ...f, confirmPass: e.target.value };
});
}}
value={form.confirmPass}
/>
<Styled.InputLabel htmlFor="user-confirmPass">
Confirme sua senha
</Styled.InputLabel>
</Styled.InputWrapper>
<Styled.newUser onClick={handleLogin}>
Já tem cadastro?? faça o login agora!
</Styled.newUser>
<Styled.SubmitButton
type="submit"
value={loading ? 'Cadastrando...' : 'Cadastrar'}
disabled={loading}
/>
</Styled.Form>
</Styled.Wrapper>
);
};
and this was my function who call back-end
const handleSchedule = async (
name: string,
email: string,
password: string,
age: number,
) => {
const scheduleResponse = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}${process.env.NEXT_PUBLIC_API_REGISTER}`,
{
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: JSON.stringify({ name, email, password, age }),
},
);
if (!scheduleResponse.ok) {
const getError = await scheduleResponse.text();
getError.includes('E11000')
? setErrorSchedule('Email já cadastrado')
: setErrorSchedule(getError);
setTimeout(() => {
setErrorSchedule('');
}, 5000);
return;
}
dispatch({ type: 'TRIGGER' });
alert('Cadastrado com sucesso.Por favor, faça o login');
};
but it return to me "password is a required field".
to see if its an back-end error, i create a playground file who do a fetch to the api with the values, and it return a 200
const email = 'emailtest#gmail.com'
const password = 'testpass123'
const age =19
const name= 'testename'
fetch('register',{method:'POST',
headers: {
'Content-Type': 'application/json'
},
body:JSON.stringify({email,password,age,name})
}).then(res=>res.text()).then(text=> console.log(text))
it just happen on the production mode, on localhost work as expect.
someone know what are happening here?

Joi: Cannot change error message for an optional array where if there are items, they cannot be empty strings

I have a form for a project where the description is required, and if they want to add details, those textareas need to be filled out. But it's okay if there are no details added. So if they want to add a detail, that text area has to be filled. But an empty array is allowed.
I am having trouble overwriting the default error for the missing details, the default being "details[0]" must not be a sparse array.
Schema:
const descriptionDetailSchema = Joi.object({
description: Joi.string().required().messages({
'string.base': 'Description is required',
'string.empty': 'Description is required'
}),
details: Joi.array().items(
Joi.string().messages({
'string.empty': 'Detail is required'
})
)
});
const DescriptionAndDetailForm = forwardRef(
({ activityIndex, item, index, saveDescription, setFormValid }, ref) => {
DescriptionAndDetailForm.displayName = 'DescriptionAndDetailForm';
const {
handleSubmit,
control,
formState: { error, errors, isValid, isValidating },
getValues
} = useForm({
defaultValues: {
description: item.description,
details: item.details
},
mode: 'onBlur',
reValidateMode: 'onBlur',
resolver: joiResolver(descriptionDetailSchema)
});
useEffect(() => {
console.log('isValid changed');
console.log({ errors, isValid, isValidating });
setFormValid(isValid);
}, [isValid]);
useEffect(() => {
console.log('errors changed');
console.log({ errors, isValid, isValidating });
}, [errors]);
useEffect(() => {
console.log('isValidating changed');
const { error, value } = descriptionDetailSchema.validate(getValues());
console.log({ error, value, errors, isValid, isValidating });
}, [isValidating, errors]);
const initialState = item;
function reducer(state, action) {
switch (action.type) {
case 'updateField':
return {
...state,
[action.field]: action.value
};
case 'addDetail': {
const newDetail = newDescriptionDetail();
return {
...state,
details: [...state.details, newDetail]
};
}
case 'removeDetail': {
const detailsCopy = [...state.details];
detailsCopy.splice(action.index, 1);
return {
...state,
details: detailsCopy
};
}
case 'updateDetails': {
const detailsCopy = [...state.details];
detailsCopy[action.detailIndex].detail = action.value;
return {
...state,
details: detailsCopy
};
}
default:
throw new Error(
'Unrecognized action type provided to DescriptionAndDetailForm reducer'
);
}
}
const [state, dispatch] = useReducer(reducer, initialState);
const handleDescriptionChange = e => {
dispatch({
type: 'updateField',
field: 'description',
value: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
saveDescription(activityIndex, index, state);
handleSubmit(e);
};
const handleAddDetail = () => {
dispatch({ type: 'addDetail' });
};
const handleDeleteDetail = (descriptionIndex, detailIndex) => {
dispatch({ type: 'removeDetail', index: detailIndex });
};
const handleDetailChange = (e, i) => {
dispatch({
type: 'updateDetails',
detailIndex: i,
value: e.target.value
});
};
return (
<form
index={index}
key={`activity${activityIndex}-index${index}-form`}
onSubmit={onSubmit}
>
<Controller
key={`activity${activityIndex}-index${index}`}
name="description"
control={control}
render={({ field: { onChange, ...props } }) => (
<TextField
{...props}
label="Description"
multiline
rows="4"
onChange={e => {
handleDescriptionChange(e);
onChange(e);
}}
errorMessage={errors?.description?.message}
errorPlacement="bottom"
/>
)}
/>
{state.details.map(({ key, detail }, i) => (
<Review
key={key}
onDeleteClick={ () => handleDeleteDetail(index, i) }
onDeleteLabel="Remove"
skipConfirmation
ariaLabel={`${i + 1}. ${detail}`}
objType="Detail"
>
<div>
<Controller
name={`details.${i}`}
control={control}
render={({ field: { onChange, ...props } }) => (
<TextField
{...props}
id={`${activityIndex}-detail${i}`}
name={`details.${i}`}
label="Detail"
value={detail}
multiline
rows="4"
onChange={e => {
handleDetailChange(e, i);
onChange(e);
}}
errorMessage={errors?.details && errors?.details[i]?.message}
errorPlacement="bottom"
/>
)}
/>
</div>
</Review>
))}
<div>
<Button
key={`activity${activityIndex}-index${index}-add-metric`}
onClick={handleAddDetail}
>
<Icon icon={faPlusCircle} />
Add Detail
</Button>
</div>
<input
type="submit"
ref={ref}
hidden
/>
</form>
);
}
);

Child components not updating global state

I am developing a form with controlled components, Material-UI and, react hooks. All of the form data is saved in a global state via a useState setter function.
Additionally, some fields need to be toggled on and off depending on the user's response which implies that its local and global state has to be reset when toggled off.
That said when two or more components are toggled off at the same time one of them fails to update the global form state.
Here is my code:
App.js
imports...
function App () {
const [formState, setFormState] = useState({
fullName: '',
email: '',
ageLevel: '',
numTitle: '',
titleTypes: '',
materialType: '',
subjInterest: ''
})
const handleTxtFldChange = (e, name) => {
setFormState({ ...formState, [name]: e.target.value })
}
return (
<>
<div className='App'>
<form noValidate autoComplete='off'>
<TextField
required
value={formState.fullName}
onChange={e => handleTxtFldChange(e, 'fullName')}
label='fullName:'
/>
<AgeLevelSelect
formState={formState}
setFormState={setFormState}
/>
<NumOfTitles
formState={formState}
setFormState={setFormState}
ageLevel={formState.ageLevel}
/>
<MaterialType
formState={formState}
setFormState={setFormState}
ageLevel={formState.ageLevel}
/>
<SubjOfInterest
formState={formState}
setFormState={setFormState}
ageLevel={formState.ageLevel}
materialType={formState.materialType}
/>
<Button
onClick={() => { submitForm() }}
>
Submit
</Button>
</form>
</div>
</>
)
}
export default App
When Adult is selected from AgeLevelSelect, numTitle and materialType will be toggled on.
The data is saved in its local and global sate.
Component: AgeLevelSelect.js
imports...
const AgeLevelSelect = ({ formState, setFormState }) => {
const [age, setAge] = useState('')
const handleChange = (event) => {
setAge(event.target.value)
setFormState({ ...formState, ageLevel: event.target.value })
}
return (
<FormControl>
<InputLabel>Age level?</InputLabel>
<Select
value={age}
onChange={handleChange}
>
<MenuItem value='School-Age'>School-Age</MenuItem>
<MenuItem value='Teens'>Teens</MenuItem>
<MenuItem value='Adults'>Adults</MenuItem>
</Select>
</FormControl>
)
}
export default AgeLevelSelect
Here we select two from the select options. The data is saved in its local and global sate.
Component: NumOfTitles.js
imports...
const NumTitles = ({ formState, setFormState, ageLevel }) => {
const [titles, setTitles] = useState('')
const [isVisible, setIsVisible] = useState('')
const handleChange = (event) => {
setTitles(event.target.value)
setFormState({ ...formState, numTitle: event.target.value })
}
useEffect(() => {
if (ageLevel === 'Adults') {
setIsVisible(true)
} else {
setValue('')
setIsVisible(false)
setFormState(prevState => {
return { ...formState, materialType: '' }
})
}
}, [ageLevel])
useEffect(() => {
if (ageLevel !== 'Adults') {
setFormState(prevState => {
return { ...formState, materialType: '' }
})
setValue('')
setIsVisible(false)
}
}, [value])
return (
isVisible &&
<FormControl>
<InputLabel id='demo-simple-select-label'>Number of titles:</InputLabel>
<Select
value={titles}
onChange={handleChange}
>
<MenuItem value='One'>One</MenuItem>
<MenuItem value='Two'>Two</MenuItem>
</Select>
</FormControl>
)
}
export default NumTitles
If you made it this far THANK YOU. We are almost done.
Here we select Non-fiction. Data gets save in local and global state.
Additionally, the subject of interest question is toggled on.
Component: MaterialType.js
imports...
const TypeOfMaterial = ({ formState, setFormState, ageLevel }) => {
const [value, setValue] = useState('')
const [isVisible, setIsVisible] = useState('')
const handleChange = (event) => {
setValue(event.target.value)
setFormState({ ...formState, materialType: event.target.value })
}
useEffect(() => {
if (ageLevel === 'Adults') {
setIsVisible(true)
} else {
setValue('')
setIsVisible(false)
setFormState(prevState => {
return { ...formState, materialType: '' }
})
}
}, [ageLevel])
useEffect(() => {
if (!isVisible) {
setFormState(prevState => {
return { ...formState, materialType: '' }
})
setValue('')
setIsVisible(false)
}
}, [isVisible])
return (
isVisible &&
<FormControl component='fieldset'>
<FormLabel component='legend'>Select type of material:</FormLabel>
<RadioGroup name='MaterialTypes' value={value} onChange={handleChange}>
<FormControlLabel
value='Mystery'
control={<Radio />}
label='Mystery'
/>
<FormControlLabel
value='NonFiction'
control={<Radio />}
label='Non-fiction'
/>
</RadioGroup>
</FormControl>
)
}
export default TypeOfMaterial
Finally, we write World War II, in the text field. The data is saved in its local and global sate.
Component: SubjOfInterest.js
imports...
import React, { useState, useEffect } from 'react'
import TextField from '#material-ui/core/TextField'
const SubjOfInterest = ({ formState, setFormState, ageLevel, materialType }) => {
const [textField, setTextField] = useState('')
const [isVisible, setIsVisible] = useState('')
const handleTxtFldChange = (e) => {
setTextField(e.target.value)
setFormState({ ...formState, subjInterest: e.target.value })
}
useEffect(() => {
if (formState.materialType === 'NonFiction') {
setIsVisible(true)
} else {
setIsVisible(false)
}
}, [materialType])
useEffect(() => {
if (formState.materialType !== 'NonFiction') {
setTextField('')
}
}, [ageLevel])
return (
isVisible &&
<TextField
value={textField}
onChange={e => handleTxtFldChange(e)}
label='Indicate subjects of interest:'
/>
)
}
export default SubjOfInterest
At this point the global state looks as follow:
{
fullName:"Jhon Doe",
ageLevel:"Adults",
numTitle:"Two",
materialType:"NonFiction",
subjInterest:"World War"
}
Then if a user changes the selected option (Adults) from the AgeLeveleSelect to a different option (teens for example) the a part of global state (numTitle, materialType, subjInterest) is expected to be cleared, instead I get this:
{
fullName:"Jhon Doe",
ageLevel:"Teens",
numTitle:"Two",
materialType:"",
subjInterest:"World War"
}
Any ideas?
I have tried many things without results.
If anyone can help will be greatly appreciated!!!
You are only clearing the materialType field:
On the NumOfTitles.js file you are setting the field "materialType" instead of the "numOfTitles" field.
On the SubjOfInterest.js you are not clearing any fields.
My suggestion is that you check the "Adults" condition on the parent component. This way you will not update the same state three times (if this occurs at the same time, this can cause some problems).
You can try doing this way:
App.js
function App () {
const [formState, setFormState] = useState({
fullName: '',
email: '',
ageLevel: '',
numTitle: '',
titleTypes: '',
materialType: '',
subjInterest: ''
})
useEffect(() => {
if(formState.ageLevel !== 'Adults') {
setFormState({
...formState,
numTitle: '',
materialType: '',
subjInterest: '',
})
}
}, [formState.ageLevel]);
// ...

React-Admin: How to send input values that have been auto filled from an API call?

I have an input 'A' that fetches address data from an API and auto fills inputs 'B' 'C' and 'D' based on that data, but after the inputs have been filled and I try to send that form to my backend, none of those auto filled inputs are sent, just the input 'A' is sent. Furthermore, if i manually edit any of the inputs (remove a char, add a space, change the value) the ones that I edited get sent to my backend.
I'm using a reducer to store the state. The inputs that I'm using are all just normal react-admin TextInput components.
Here's the code:
const AutoFill = () => {
const [searching, setSearching] = useState(false);
const [error, setError] = useState(false);
const [stateData, setStateData] = useReducer(
(state, newState) => ({ ...state, ...newState }),
{
cep: ' - ',
address: '',
number: '',
neighborhood: '',
city: '',
state: '',
}
);
const FormControl = (event) => {
const { name, value } = event.target;
setStateData({ [name]: value });
};
const SearchControl = (event) => {
const { name, value } = event.target;
setStateData({ [name]: value });
if (value && !value.includes('_')) {
setSearching(true);
setStateData({ state: '...' });
setStateData({ city: '...' });
setStateData({ neighborhood: '...' });
setStateData({ address: '...' });
cep(value.replace('-', '')).then(
(result) => {
setSearching(false);
setError(false);
setStateData({ state: result.state });
setStateData({ city: result.city });
setStateData({ neighborhood: result.neighborhood });
setStateData({ address: result.street });
},
() => {
setSearching(false);
setError(true);
setStateData({ state: '' });
setStateData({ city: '' });
setStateData({ neighborhood: '' });
setStateData({ address: '' });
}
);
}
};
return (
<>
<TextInput
source="cep"
error={error}
value={stateData.cep}
onChange={SearchControl}
/>
<TextInput
source="address"
disabled={searching}
value={stateData.address}
onChange={FormControl}
/>
<TextInput
source="number"
disabled={searching}
value={stateData.number}
onChange={FormControl}
/>
<TextInput
source="neighborhood"
disabled={searching}
value={stateData.neighborhood}
onChange={FormControl}
/>
<TextInput
source="state"
disabled={searching}
value={stateData.state}
onChange={FormControl}
/>
<TextInput
source="city"
disabled={searching}
value={stateData.city}
onChange={FormControl}
/>
</>
);
};
export const Create = (props) => {
return (
<Create {...props}>
<SimpleForm>
<NumberInput label="Value" source="price" />
<AutoFill />
<RichTextInput label="Description" source="description" />
</SimpleForm>
</Create>
);
};
You're going to need to use React Final Form's FormState and Form solutions. Will use snippets of my code for example.
1) Grab the form values
const formState = useFormState();
const form = useForm();
const {
asset_system_parent_id: majorSystem,
classification,
} = formState.values;
2) Setup useEffect that will observe changes to a form field:
useEffect(() => {
const setFluidEnd = async () => {
DO SOMETHING!!!!!
};
if ('Fluid End Maintenance' === classification) {
setFluidEnd();
}
}, [classification, form, notify]);
3) Use form.change (+ form.batch if you need to update multiple inputs)
useEffect(() => {
const setFluidEnd = async () => {
await requestGetList('asset-systems', 'id', 'ASC', 500, {
description: 'Fluid End',
relationship: 'parent',
})
.then(res => {
form.change('asset_system_parent_id', res.data[0].id);
})
.catch(error => {
notify(`System Assets not found`, 'warning');
});
};
if ('Fluid End Maintenance' === classification) {
setFluidEnd();
}
}, [classification, form, notify]);
You can read more about the api here: https://final-form.org/docs/final-form/types/FormApi
Please use this code.
-index.js file
import axios from "axios";
export const setInputValue = (data) => {
return axios.get(`https://www.example.com/profile`)
.then((response) => {
return response.data;
});
};
-component.js
return setInputValue(value).then(() => {
this.setState(() => ({
loading: false
}));
});
...
render(){
return (
...
<input type="text" onClick={e => this.onClick(e)} value={this.state.value}/>
..
)}
...
react-admin.php
...
public function setInputValue(value)
{
try {
$user->set(value);
return response()->json(["result" => "successfully!"]);
} catch (\Exception $e) {
return getErrorResponse($e);
}
}

React TypeScript: Set initial value for input with useRef

The input field displays the value saved in local storage, but I can't edit the value in the input field and I don't know why.
I don't want to use a placeholder as I want to be able to edit the values.
import React, { useRef, useState } from 'react';
const ProfileComponent: React.FC = () => {
let email = useRef<HTMLInputElement>(null);
const saveEmail = () => {
localStorage.setItem('email', email)
}
// tslint:disable-next-line: no-any
const update = (event: any) => {
if (event.target.name === 'email') {
setState({ ...state, email: event.target.value });
} else if (event.target.name === 'fullName') {
setState({ ...state, fullName: event.target.value });
}
};
interface StateInterface {
email: string;
}
const [state, setState] = useState<StateInterface>({
email: localStorage.getItem('email') || '',
});
return (
<input type='text' name='fullName' ref={fullName} onChange={update} value={state.fullName} />
<input type='text' name='email' ref={email} onChange={update} value={state.email} />
<button onClick={saveEmail}></button>
)
}
There are a few issues with the code you have provided
1) You should wrap the DOM elements with React Fragments (<> </>)
2) Instead of setting the type of event as any, you might want to use React.FormEvent<HTMLInputElement>.
3) You should use localStorage.setItem('email', state.email) instead of localStorage.setItem('email', email), since email is a property as part of the state object, thus you will have to reference it in order to access the values.
Here are the full changes below:
interface StateInterface {
email: string;
fullName: string;
}
const ProfileComponent: React.FC = () => {
let email = useRef<HTMLInputElement>(null);
let fullName = useRef<HTMLInputElement>(null);
const [state, setState] = useState<StateInterface>({
email: 'aa#gmail.com' || '',
fullName: 'aa' || '',
});
const saveEmail = () => {
localStorage.setItem('email', state.email)
console.log(state);
}
const update = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.name === 'email') {
setState({ ...state, email: event.target.value });
} else if (event.target.name === 'fullName') {
setState({ ...state, fullName: event.target.value });
}
};
return <>
<input type='text' name='fullName' ref={fullName} onChange={update} value={state.fullName} />
<input type='text' name='email' ref={email} onChange={update} value={state.email} />
<button onClick={saveEmail}>save</button>
</>
}
You have to have an onChange in your input
return (
<input type='text' name='email' ref={email} onChange={e => setState({email: e.target.value})}
value= {state.email} />
<button onClick={saveEmail}></button>
)

Resources