One input field, different name and validation react hook form - javascript

I have a form created with React hook form and input field thant can have 2 different values: a pec email or a SDI code with different validation.
I get error with validation: keep telling me that sdi should be 7 characters but the name is set to "pecEmail". Also at page load "name" is set to "sdiCode" but if an email is already there, should be "email". It changes when I click the input.
I tried to pass the name field in register but I lost all the validation
ref={register(isPec ? 'pecEmail' : 'sdiCode', {
code here:
const [isPec, setIsPec] = useState()
const sdiOrPec = watch(['sdiCode', 'pecEmail'])
useEffect(() => {
setIsPec(
(sdiOrPec.sdiCode && sdiOrPec.sdiCode.includes('#')) ||
(sdiOrPec.pecEmail && sdiOrPec.pecEmail.includes('#'))
)
}, [watch])
<div className='input-container'>
<div
className={`input-container__item ${
errors[isPec ? 'pecEmail' : 'sdiCode'] && 'has-error'
}`}
>
<label>{t('components.BillingInfoForm.sdiLabel')}</label>
<input
type='text'
name={!isPec ? 'sdiCode' : 'pecEmail'}
defaultValue={
get(billingInformations, 'sdiCode')
? get(billingInformations, 'sdiCode')
: get(billingInformations, 'pecEmail')
? get(billingInformations, 'pecEmail')
: ''
}
placeholder={t('components.BillingInfoForm.sdiPlaceholder')}
ref={register({
maxLength: {
value: isPec ? 320 : 7,
message:
!isPec &&
t('components.BillingInfoForm.sdiMessageError'),
},
minLength: {
value: isPec ? 3 : 7,
message:
!isPec &&
t('components.BillingInfoForm.sdiMessageError'),
},
validate: {
value: (value) => {
if (isPec && sdiOrPec.pecEmail) {
return (
isEmailValid(sdiOrPec.pecEmail) ||
t('components.SignupForm.emailError')
)
}
},
},
})}
/>
{errors[isPec ? 'pecEmail' : 'sdiCode'] && (
<small>
{errors[isPec ? 'pecEmail' : 'sdiCode'].message}
</small>
)}
</div>
</div>
Thanks

Related

Is rendering multiple components by keeping them in objects or arrays a good approach?

In a specific case, I had to render some UI in a specific skeleton/format. For that case I decided to make an array of objects, in which each object has some specific UI components, which are supposed to be rendered on the UI. I was able to see the expected result, but as we know react components re-render on each state update, and all the functions and variables are reinstantiated.
If the components inside the array are dependent on states of the component in which they are used, it will keep on rerendering the array as well. Will it cause massive performance issues?
I decided to use hooks like useMemo and wrap the array of components in it and by putting the states necessary for components to work in the dependency array of useMemo, but there are way too many states. What is the correct way to render Components in such cases ?
Please take a look at the code mentioned below to get some context.
Code:
Return function
<div className='create-action'>
{
loading
?
loadingComponent
? loadingComponent
:<div className='action-loader'> <CirclesLoader/> </div>
: <>
{
preview
? PREVIEW.map((item, index) => (
<React.Fragment key={item.id}>
{item.content}
</React.Fragment>
))
: CREATION.map((item, index) =>
item.hidden
? null
: <React.Fragment key={item.id}>
{item.content}
</React.Fragment>
)
}
{
showWarning
? <NewPromptDialog
is_active={showWarning}
heading="Cancel Action?"
message="Your progress will be lost. Do you still wish to continue?"
secondary_button_text="No"
primary_button_text="Cancel Action"
onClose={() => setShowWarning(false)}
onAccept={closeCreateAction}
button_right_aligned={true}
defaultPromptCtaClasses="create-action-button-delete-prompt"
/>
: null
}
</>
}
</div>
CREATION array used in Rendering
const CREATION = [
{
id: 1,
content:
<div className='header'>
<span className="header-text">
{
page === TEMPLATE
? <span className='header-back-cta' onClick={
JSON.stringify(orgActionTemplateData) === JSON.stringify(actionTemplateData)
? closeCreateAction
: () => setShowWarning(true)
}><Back/></span>
: null
}
Create Action
</span>
<span className="icon" onClick={
JSON.stringify(orgActionTemplateData) === JSON.stringify(actionTemplateData)
? closeCreateAction
: () => setShowWarning(true)
}>
<Close />
</span>
</div>
},
{
id: 2,
content:
<div className='middle'>
{
DATA_BODY.length
? DATA_BODY.map((data, index) => {
return (
!data.hidden
? data.type === 'warning' // To display WarningCard in Chat
? data.content ? data.content : null
: <>
<div key={index} className="middle-block ">
{data.title ? <div className="middle-title">{data.title}</div> : null}
{data.subtitle ? <div className="middle-subtitle">{data.subtitle}</div> : null}
{data.content ? data.content : null}
{data.focusContent && data.focus ? data.focusContent : null}
{
data.validationError && data.errorContent
? <p className={showValidationErrors ? `middle-error-content` : 'middle-warning-content'}><img src={showValidationErrors ? information_circle : warning_information_circle} height="13px" width="13px" alt="info-circle" />{data.errorContent}</p>
: null
}
</div>
<div className='middle-bottom-divider' />
</>
: null
)
})
: null
}
</div>
},
{
id: 3,
content: <div className={'footer'}>
<div data-for="create-cta" data-tip="Please fill the required fields" className={classNames({'disabled-cursor': (apiCall || disableCreateCTA)})} onClick={disableCreateCTA ? () => {
validateForm('errors');
} : () => {}}>
<Button
type="primary"
label={"Create"}
disabled={apiCall || disableCreateCTA} // Add disable logic here
onClick={!apiCall ? onClickHandler : () => {}}
iconAfterLabel={forward_system_white}
btnClasses={loading ? "is-loading" : null}
/>
{disableCreateCTA ? <ReactTooltip effect="solid" place="left" id="create-cta" /> : null}
</div>
</div>
}
]
DATA_BODY array used in CREATION array object
const DATA_BODY = [
{
id: DATA_BODY_ID++,
type: 'warning',
content: <WarningCard
message={"Only the details related to the action will be shared with the assignee. No confidential information or data from the chat will be shared."}
icon={info_icon}
type={"info"}
/>,
hidden: (page && ![CRISP_CHAT, IJP].includes(page))
},
{
id: DATA_BODY_ID++,
title: "Summary*",
focus: summaryFocus,
subtitle: "Give a summary that is crisp and clear",
content:<input id="summary" className={`input ${(showValidationErrors || showValidationWarning) && !actionTemplateData.summary ? showValidationErrors ? 'input-error' : 'input-warning' : ''}`} name='summary' value={actionTemplateData.summary} required autoComplete='off' onChange={onChangeHandler} onFocus={() => setSummaryFocusValue(true)} onBlur={() => setSummaryFocusValue(false)} maxLength={80} placeholder="E.g. Provide more opportunities to learn and grow" />,
focusContent: <label className={classNames('error-status',{'warning': actionTemplateData.summary?.length >= 70 && actionTemplateData.summary?.length < 80}, {'error': actionTemplateData.summary?.length === 80 })}>{actionTemplateData.summary?.length ? actionTemplateData.summary.length : 0} / 80</label>,
validationError: (showValidationErrors || showValidationWarning) && !actionTemplateData.summary,
errorContent: 'Please enter the summary'
},
{
id: DATA_BODY_ID++,
title: "Description*",
subtitle: "Add more details about the action",
content: <TextArea
placeholder='E.g. Make your workforce future ready'
classes={`input ${(showValidationErrors || showValidationWarning) && !actionTemplateData.description ? showValidationErrors ? 'input-error' : 'input-warning' : ''}`}
name='description'
id="description"
value={actionTemplateData.description}
required
autoComplete='off'
onChange={onChangeHandler}
maxLength={1024}
auto_grow
re_size="none"
/>,
validationError: (showValidationErrors || showValidationWarning) && !actionTemplateData.description,
errorContent: 'Please enter the description'
},
{
id: DATA_BODY_ID++,
title: "Assignee*",
subtitle: "This person will be notified and be responsible for completing the action",
content: <div className='create-action-assignee-dropdown' name='assignee-dropdown'>
{
[IJP, CRISP_CHAT].includes(page) && assigneeListForCrispChat?.length
? <AssigneeDropdownComponent
id={"assignee-dropdown"}
assigneeCategories={assigneeListForCrispChat}
click_mode={true}
trigger_close={assigneeCrispChatDropdownTrigger}
selected_item_text={selectedAssignee && selectedAssignee[0]?.value ? selectedAssignee[0].label : "Select Assignee" }
no_selection_text={'Select Assignee'}
classes={`crisp-chat-assignee-dropdown`}
dropdown_button_class={(showValidationErrors || showValidationWarning) && actionTemplateData.assignee_id === null ? showValidationErrors ? 'input-error' : 'input-warning' : ''}
onClick={(assignee) => {
setSelectedAssignee(assignee);
setAssigneeCrispChatDropdownTrigger(trigger => !trigger);
}}
onOutsideClick={() => {}}
/>
: assigneeDropdownData && Object.keys(assigneeDropdownData).length
? <span><MultiCheckboxDropdown data={assigneeDropdownData} /></span>
: null
}
</div>,
// hidden: ((["IJP", "Crisp Chat"].includes(page) && !assigneeListForCrispChat.length) || !(assigneeList && assigneeList.list_items && assigneeList.list_items.length > 0)) ? true : false,
validationError: (showValidationErrors || showValidationWarning) && actionTemplateData.assignee_id === null,
errorContent: 'Please select the assignee'
},
{
id: DATA_BODY_ID++,
title: "Start Date*",
subtitle: "Set the start date for the action",
content:
<div className="create-action-date-picker" name='start-date'>
<CalenderDropdown
id={"startDate-dropdown"}
date={actionTemplateData.startDate ? new Date(moment.unix(actionTemplateData.startDate).format('DD MMM YYYY')) : null}
onChange={(date) => setDatePeriod("startDate", date)}
no_selection_text={'Select Start Date'}
selected_item_class='calendar-dropdown-selected-text'
selected_item_text={actionTemplateData.startDate ? moment.unix(actionTemplateData.startDate).format('DD MMM YYYY') : 'Select Start date'}
// classes={'is-left cal-dropdown dropdown-fit-content'}
dropdown_button_class={(showValidationErrors || showValidationWarning) && actionTemplateData.startDate === null ? showValidationErrors ? 'input-error' : 'input-warning' : ''}
minDate={moment().hour() < 21 ? new Date(new Date().setDate(new Date().getDate())) : new Date(new Date().setDate(new Date().getDate() + 1))}
click_mode={true}
trigger_close={startDateTrigger}
customIcon={calendar_icon}
className={'date-action-creation'}
/>
{(showValidationErrors || showValidationWarning) && actionTemplateData.startDate === null ? <p className={`${showValidationErrors ? 'middle-error-content' : 'middle-warning-content'} mg-top-5`}>
<img src={showValidationErrors ? information_circle : warning_information_circle } height="13px" width="13px" alt="info_circle" />
Please select the start date
</p> : null}
</div>
},
{
id: DATA_BODY_ID++,
title: "Due Date*",
subtitle: "Set the due date for the action",
content: <div className="create-action-date-picker" name='end-date'>
<CalenderDropdown
id={"endDate-dropdown"}
date={actionTemplateData.endDate ? new Date(moment.unix(actionTemplateData.endDate).format('DD MMM YYYY')) : null}
onChange={(date) => setDatePeriod("endDate", date)}
no_selection_text={'Select Due Date'}
selected_item_class='calendar-dropdown-selected-text'
selected_item_text={actionTemplateData.endDate ? moment.unix(actionTemplateData.endDate).format('DD MMM YYYY') : "Select Due Date"}
// classes={'is-left cal-dropdown dropdown-fit-content'}
dropdown_button_class={(showValidationErrors || showValidationWarning) && actionTemplateData.endDate === null ? showValidationErrors ? 'input-error' : 'input-warning' : ''}
minDate={actionTemplateData.startDate ? new Date(moment.unix(actionTemplateData.startDate)) : moment().hour() < 21 ? new Date(new Date().setDate(new Date().getDate())) : new Date(new Date().setDate(new Date().getDate() + 1))}
click_mode={true}
trigger_close={endDateTrigger}
customIcon={calendar_icon}
className={'date-action-creation'}
/>
{(showValidationErrors || showValidationWarning) && actionTemplateData.endDate === null ? <p className={`${showValidationErrors ? 'middle-error-content' : 'middle-warning-content'} mg-top-5`}>
<img src={showValidationErrors ? information_circle : warning_information_circle } height="13px" width="13px" alt="info_circle" />
Please select the due date
</p> : null}
</div>
},
{
id: DATA_BODY_ID++,
title: "Checklist items*",
subtitle: "Break down your action into bite sized checklist items (At least 1 required)",
content: <AddMore
id={'checklist-items'}
data={checklistItems}
placeholder="E.g. Recognize 3 team members in the all hands"
addCTALabel="Add Checklist Items"
minField={1}
customDisableAddCTA={!checklistItems?.filter(list => list !== "")?.length || checklistItems?.filter(list => list !== "")?.length === 10}
validationError={showValidationErrors && checklistItems.filter(item => item === '')?.length}
validationWarning={showValidationWarning && checklistItems.filter(item => item === '')?.length}
tooltipData={checklistItems.length === 10 ? 'You can add a maximum of 10 Checklist items.' : 'Fill the empty fields <br/> before creating new ones'}
errorContent={'Please add at least 1 checklist item'}
onChange={(checklist = ['']) => {
if (checklist) {
setChecklistItems(checklist);
}
}}
/>
},
{
id: DATA_BODY_ID++,
title: "Tag Drivers & Elements",
subtitle: "Associate Drivers and Elements with the action",
content: <div className='create-action-assignee-dropdown'><MultiCheckboxDropdown data={driverElementsDropdown} classses="is-up" /></div>
},
{
id: DATA_BODY_ID++,
title: "Additional resources",
subtitle: "Add links to any reference videos or articles",
content: <div className="padding-bottom-50 fullwidth" ><AddMore
data={referenceLinks}
placeholder="E.g. www.example.com/blog"
addCTALabel="Add Link"
customDisableAddCTA={referenceLinks.filter(link => !isValidURL(link)).length > 0}
validationError={showValidationErrors && referenceLinks.filter(link => link !== "").length}
validationWarning={showValidationWarning && referenceLinks.filter(link => link !== "").length}
tooltipData={'Fill the empty fields <br/> before creating new ones'}
errorContent={'Please enter a valid resource link'}
type="ref_links"
errorIndexItem={referenceLinks.findIndex(link => !isValidURL(link))}
onChange={(r) => {
if (r) {
setReferenceLinks(r)
}
}}
/> </div>
}
]

How to set values for react-select with dynamic rendering of select fields?

I was working on rendering dynamic select fields and trying to set the values for them . Let me explain clearly what i am trying .
At first i have this useState :
const add_actions_options = [
{value : "val1" , label:VAL1},
{value : "val2" , label:VAL2 })}
]
const [ actions , setActions ] = useState<any | undefined>([{type : add_actions_options[0].value , label : "" , data : ""} ])
Initially 1 select field is rendered , but later i have a button which renders more select fields ( Max 4 select fields ) below is the related code for dynamic rendering of Select fields fields:
function addAction(){
if(actions.length < 4 ){
setActions([...actions , {type : add_actions_options[0].value , label : "" , data : ""}])
} else {
toast('max 4 allowed', { type: "error" })
}
}
const handleTypeChange = (index, value) => {
const updatedFields = [...actions];
updatedFields[index].type = value;
setActions(updatedFields);
}
const handleLabelChange = (index , value) => {
const updatedFields = [...actions];
updatedFields[index].label = value;
setActions(updatedFields);
}
const handleDataChange = (index, value) => {
const updatedFields = [...actions];
updatedFields[index].data = value;
setActions(updatedFields);
}
<button className='btn btn-primary btn-sm btn-block' onClick={() => addAction()}>{intl.formatMessage({id: 'ADD.ACTION'})}</button>
<div className='row my-6 '>
{actions.map((item , index) => {
return(
<div key={index} className='row my-6'>
<div className='col-4'>
<h4><label className="form-label">{intl.formatMessage({ id: 'TEMPLATE.TYPE' })}*</label></h4>
<Select
defaultValue={add_actions_options[0]}
onChange={(value) => handleTypeChange(index, value)}
options={add_actions_options}
/>
</div>
<div className='col-4'>
<h4><label className="form-label">{intl.formatMessage({ id: 'TEMPLATE.LABEL' })}*</label></h4>
<input
className="form-control form-control-lg form-control-solid"
name='action.label'
type="text"
maxLength={30}
onChange={(event) => handleLabelChange(index, event.target.value)}
value={actions.label}
required
/>
</div>
<div className='col-4'>
<h4><label className="form-label">{intl.formatMessage({ id: 'TEMPLATE.DATA' })}*</label></h4>
<input
className="form-control form-control-lg form-control-solid"
name='action.data'
type="text"
maxLength={100}
onChange={(event) => handleDataChange(index, event.target.value)}
value={actions.data}
required
/>
</div>
</div>
)
})}
</div>
Here's what i wanted to achieve , the data and label values for actions are working as expected , but the value is being set differently when i choose options from select dropdown,
Initially if i do not change any values for select component it is working as expected , the output :
0: {type:'val1' , label : 'qwe' , data:'ujm'}
1: {type:'val1' , label : 'ujhn' , data: 'uhn'}
Note : In above scenario I am just rendering one more select fields and initially the type is set to add_actions_options[0].value , but if I try to select val2 from the dropdown the output turns to :
0: {type: {value: 'val2', label: 'VAL2'} , label : 'qwe' , data:'ujm'}
1: {type:'val1' , label : 'ujhn' , data: 'uhn'}
//DESIRED OUTPUT WHEN I CHOOSE DIFFERENT OPTION IN SELECT TAG
0: {type: 'val2' , label : 'qwe' , data:'ujm'}
1: {type:'val1' , label : 'ujhn' , data: 'uhn'}
I just want the val2 to be placed within type , but its placing whole selected option , can anyone help me pass this ? Would be great help for me !
Regards

State not storing input data if value is given

I am trying to have an initial value in my input field before the user edits it. But when an initial value is given, it does erase when I try to remove it.
<Input
type="text"
placeholder={
title === "" && fieldsFilled === false
? "This is a required field"
: "Enter Title"
}
id="title"
onChange={(e) => setTitle(e.target.value)}
style={
title === "" && fieldsFilled === false
? styles
: null
}
/>
Why is my state not updating when I change. What is wrong with the onChange?
Dont add initial value on placeholder use value to add it and initial value should come from hook as below
const [title, setTitle] = useState('initial value')
<Input
type="text"
placeholder={
title === "" && fieldsFilled === false
? "This is a required field"
: "Enter Title"
}
id="title"
value = {title}
onChange={(e) => setTitle(e.target.value)}
style={
title === "" && fieldsFilled === false
? styles
: null
}
/>
look on how i added the value = {title} on input tag, thats the key

How to make a onChange calling only with the submit button?

I have a onChange that calls every time i select an option, but i need it to call only when i click in the submit button with all fullfilled preferences. I think this may be pretty simple, but feel im missing some point... The code below is very simplified, but if solve this, it solve the remaining issues. Im using react-js, Formik and Styled Components.
I tried put the onChange in the button, but it dont fill "render"...
Is there any way to call the onSubmit inside the onChange?
The options(in json):
[
{ "value": "tracker_itens", "label": "Listagem de Veículos", "render": 1 },
{
"value": "traveled_kms",
"label": "Listagem de KM Percorridos",
"render": 2
},
{
"value": "offline_itens",
"label": "Listagem de Itens Offlines",
"render": 3
},
{
"value": "maintenance",
"label": "Listagem de Manutenções",
"render": 4
},
{
"value": "supply",
"label": "Listagem de Abastecimentos",
"render": 5
},
{
"value": "violation",
"label": "Listagem de Infrações",
"render": 6
},
{
"value": "idle_itens",
"label": "Listagem de Itens Ociosos",
"render": 7
},
{ "value": "history", "label": "Listagem de Histórico", "render": 8 }
]
The code(
as simplified as possible):
function Reports() {
registerLocale("pt-BR", el);
const formik = useFormik({
initialValues: {
render: 0
},
onSubmit: values => {
console.log(values);
}
});
function renderType(option) {
formik.setFieldValue("render", option.render);
}
return (
<ContainerStyled>
<ReportMainContainer>
<ContainerFilters>
<form onSubmit={formik.handleSubmit}>
<HeaderFilter>RELATÓRIOS</HeaderFilter>
<BodyFilter>
<FieldContainer>
<LabelStyled>Tipo de Relatório</LabelStyled>
<SelectStyled
options={reportTypeOptions}
placeholder="Selecione o tipo"
noOptionsMessage={() => "Nenhuma opção encontrada"}
onChange={renderType}
/>
</FieldContainer>
{formik.values.render === 1 ? (
""
) : formik.values.render === 2 ? (
<TraveledKmsFilter />
) : formik.values.render === 3 ? (
<OfflineItensFilter />
) : formik.values.render === 4 ? (
<MaintenanceFilter />
) : formik.values.render === 5 ? (
<SupplyFilter />
) : formik.values.render === 8 ? (
<HistoryFilter />
) : (
""
)}
</BodyFilter>
<ButtonsFilter>
<FilterButton type="submit" color="#555">
Visualizar
</FilterButton>
</ButtonsFilter>
</form>
</ContainerFilters>
</ReportMainContainer>
<TableContainer>
{formik.values.render === 1 ? (
<Vehiecles />
) : formik.values.render === 2 ? (
<TraveledKms />
) : formik.values.render === 3 ? (
<OfflineItens />
) : formik.values.render === 4 ? (
<Maintenance />
) : formik.values.render === 5 ? (
<Supply />
) : formik.values.render === 8 ? (
<History />
) : (
"asdsadasd"
)}
</TableContainer>
</ContainerStyled>
);
}
Resolution that worked, for who encounter a similar issue:
Declare useState to do the work inside the onSubmit:
const [reportType, setReportType] = useState(0); //new
registerLocale("pt-BR", el);
const formik = useFormik({
initialValues: {
render: 0
},
onSubmit: values => {
setReportType(values.render); //new
}
});
Then, call "reportType" to be the condition to the ternary operator, instead of formik.values.render:
<TableContainer>
{reportType === 1 ? (
<Vehiecles />
) : reportType === 2 ? (
<TraveledKms />
) : reportType === 3 ? (
<OfflineItens />
) : reportType === 4 ? (
<Maintenance />
) : reportType === 5 ? (
<Supply />
) : reportType === 8 ? (
<History />
) : (
"asdsadasd"
)}
</TableContainer>

React Components utilization

I am trying to use the following component in my file, but cannot format options as the component would like. New to this... any ideas on how to fix my code would be greatly appreciated.
Field below the componenet is how I'm trying to use it.
/**
* Single select dropdown component
* - Uses an array passed to options prop
* - 'options' Array must have 'value' (what is submitted) & 'text'
(what is displayed)
*/
export class SingleSelect extends React.Component {
render () {
const {
input,
label,
options,
required,
placeholder,
meta: { error, warning, touched },
...props
} = this.props;
let message;
const validationState = touched && ( error && 'error' ) || ( warning && 'warning' ) || null;
if ( touched && ( error || warning ) ) {
message = <span className="help-block">{ error || warning }</span>;
}
let placeholderText = 'Select an option';
if ( placeholder ) {
placeholderText = placeholder;
}
const asterisk = required && (<span className="form-field-required" />) || null;
return (
<FormGroup validationState={ validationState }>
<ControlLabel>
{ label }
{ asterisk }
</ControlLabel>
<FormControl {...{
...input,
componentClass: 'select',
placeholder: placeholderText,
...props
}} >
<option value="">{ placeholderText }</option>
{
options.map((option, index) => (
<option key={index} value={option.value}>
{option.text}
</option>
))
}
</FormControl>
{ message }
<FormControl.Feedback />
</FormGroup>
);
}
}
<Field
name="type"
label="Type"
component={fields.SingleSelect}
options={
text=[Media File, External File]
value=type
}
required
placeholder
validate={[validators.required]}
/>
You need extra {} around the object you are passing:
<Field
name="type"
label="Type"
component={fields.SingleSelect}
options={[{
text: 'Media File'
value: type
},{
text: 'External File'
value: type
}]}
required
placeholder
validate={[validators.required]}
/>
In JSX, the first {} indicate that what's inside is JavaScript that needs to be ran. So you can kind of think about the first {} being ignored.

Categories

Resources