How to get date from IonDateTime with Ionic React - reactjs

I have recently started using react due to the performance it provides, so I'm not used to this new framework. I have searched on this exact topic but cannot find an answer.
Although the problem is very simple, (Just want to return the selected date).
Here's what I currently am trying to do:
let dateValue = format(new Date(), 'yyyy-MM-dd')+ 'T09:00:00.000Z';
const dateChanged = (value: any) => {
console.log("value: ", value);
dateValue = value;
};
const DateModal: React.FunctionComponent<any> = ({ isOpen, onClose }) => {
return (
<IonModal className="datemodal" isOpen={isOpen}>
<IonContent className="dateModalOpen">
<IonDatetime
locale="en-GB"
value={dateValue}
id="datetime"
onChange={() => dateChanged(datetime)}
showDefaultButtons={true}
min="1920"
max="2022"
className="calendar"
presentation="date"
>
<span slot="title">Date of Birth</span>
</IonDatetime>
</IonContent>
</IonModal>
);
};
I recieve an error on the "onChange" (Cannot find name 'datetime'.), this is what I used to do in Angular. I tried to use a template reference by doing "id=datetime", which in Angular was "#datetime". And in so doing would work inside the onChange event.
How do I make this work?
Thank you in advance!

Solved. On react you can use "Controller" to get the data from IonDateTime as follows:
const {
handleSubmit,
control,
setValue,
register,
getValues,
formState: { errors },
} = useForm({
defaultValues: {
fullname: "",
date: "",
gender: "MALE",
email: "",
},
});
console.log(getValues());
/**
*
* #param data
*/
const onSubmit = (data: any) => {
alert(JSON.stringify(data, null, 2));
};
const [ionDate, setIonDate] = useState("");
const dateChanged = (value: any) => {
let formattedDate = format(parseISO(value), "dd-MM-yyyy").replace(
/-/g,
"/"
);
setIonDate(formattedDate);
setshowDate({ isOpen: false });
};
const DateModal: React.FunctionComponent<any> = ({ isOpen }) => {
console.log(isOpen);
return (
<IonModal className="datemodal" isOpen={isOpen}>
<IonContent className="dateModalOpen">
<Controller
render={({ field }) => (
<IonDatetime
value={field.value}
onIonChange={(e) => dateChanged(e.detail.value)}
locale="en-GB"
onChange={dateChanged}
showDefaultButtons={true}
onIonCancel={() => setshowDate({ isOpen: false })}
min="1920"
max="2022"
className="calendar"
presentation="date"
>
<span slot="title">Date of Birth</span>
</IonDatetime>
)}
control={control}
name="date"
rules={{ required: "This is a required field" }}
/>
<IonButton type="submit">submit</IonButton>
</IonContent>
</IonModal>
);
};
};

You just need to use the ionOnChange handler:
<IonDatetime presentation="date"
id="datetime"
onIonChange={(e) => console.log(e)}>
</IonDatetime>

Related

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

Cannot set defaultValue in React-Select

I am using React-Select library in my react project, i am stuck on a point where i want to set default value on the first select option rendered in a loop.
Here is the code below for your understanding
export default function FormSection() {
const options = [
{ value: "Titular", label: "Titular", isDisabled: true },
{ value: "Conjuge", label: "Conjuge" },
{ value: "Filho(a)", label: "Filho(a)" },
];
const formik = useFormik({
initialValues: {
relation: null,
},
onSubmit: (values) => {
console.log(values);
},
});
const calcFormikValuesSelect = (index) => {
if (formik.values.relation == null) {
return null;
} else if (formik.values.relation) {
let allrelation = formik.values.relation;
return allrelation[index];
}
};
const handleChangeRelation = (selectedOption, index) => {
console.log(selectedOption, index);
formik.setFieldValue(`relation[${index}]`, selectedOption);
// formik.setFieldValue('firstSmoker', selectedOption)
};
return (
<div className="secondSection">
<form onSubmit={formik.handleSubmit}>
{Array.apply(null, { length: 3 }).map((item, index) => (
<React.Fragment>
<div className="wrapper-person-2">
<p className="tab-text">Profissão</p>
<Select
value={calcFormikValuesSelect(index)}
name={`relation[${index}]`}
onChange={(selectedOption) =>
handleChangeRelation(selectedOption, index)
}
options={options}
className="select-smoker"
/>
</div>
</React.Fragment>
))}
<button type="submit" className="button-enabled">
CONTINUE
</button>
</form>
</div>
);
}
So for index=0, i want to set default value
{value:'Titular,label:'Titular}
and disabled that Select so that dropdown does not show on click and then for index above 0 I want to show the options as options array has
I tried passing prop to React-Select like
defaultValue:{label:'Titular',value:'Titular}
but that doesn't work
"react-select": "^4.3.1",
Hope someone helps me out, thanks !
To set the default value in formik, you need to provide it in initialValues like this:
const formik = useFormik({
initialValues: {
relation: {0: options[0] } OR [options[0]], // depends on how you put it
},
onSubmit: (values) => {
console.log(values);
},
});

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]);
// ...

How to iterate children in React using render props

I have this pseudo code for my form. Where I would like to display just fields with canAccess=true.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
<div className="nestedItem">
<Field name="firstName" />
</div>
<Field name="surName" />
</>
}
</Form>
With this code I would like to see rendered just field with firstName.
I know that I can iterate through React.Children.map() but I don't know how to iterate children when using render props.
Also there can be nested elements, so I would like to find specific type of component by name.
Thanks for help.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
{
Object.keys(props.initialValues).map(k => (
k.canAccess && <Field name={k} />
));
}
</>
}
</Form>
Edit: Your form can perform some logic and pass back filtered items to your component.
getFilteredItems = items => Object.keys(items).reduce((acc, key) => {
const item = items[key];
const { canAccess } = item;
if(!canAccess) return acc;
return {
...acc,
[key]: item
}
}, {}));
render() {
const { initialValues, children } = this.props;
const filteredItems = this.getFilteredItems(initialValues);
return children(filteredItems);
}
<Form initialValues={initialValues}>
{ props =>
<>
{
Object.keys(props).map(k => <Field name={k} />)
}
</>
</Form>
This is what I was looking for.
const Form = ({initialValues, children}) =>
props =>
<Authorized initialValues={initialValues}>
{typeof children === 'function' ? children(props) : children}
</Authorized>
const Authorized = ({initialValues, children}) => {
// Do check
React.Children.map(chidlren, x => {
if(x.type === Field ) // && initialValues contains x.props.name
return x
return null
... })
}

In react hooks form, how to you copy data from one property to other of state?

This is register form, I want to send the value orgTypes>name to orgType of data of same object using onChange of select.
https://codesandbox.io/s/react-typescript-zm1ov?fontsize=14&fbclid=IwAR06ZifrKrDT_JFb0A-d_iu5YaSyuQ9qvLRgqS20JgAcSwLtAyaOFOoj5IQ
When I use onChange of Select, it erases all other data of the inputs.
import React from "react";
import { Select } from "antd";
const { Option } = Select;
const RegisterFinal = () => {
const data = {
orgName: "",
orgRegNo: "",
orgType: "",
orgTypes: [
{ id: "1", name: "Vendor" },
{ id: "2", name: "Supplier" },
{ id: "3", name: "Vendor and Supplier" }
],
errors: {}
};
const [values, setValues] = React.useState(data);
const handleChange = (e: any) => {
e.persist();
setValues((values: any) => ({
...values,
[e.target.name]: e.target.value
}));
};
const handleSubmit = () => {
console.log(values);
};
return (
<React.Fragment>
<input
name="orgName"
onChange={handleChange}
placeholder="Organization Name"
value={values.orgName}
/>
<input
name="orgRegNo"
onChange={handleChange}
placeholder="Registration Number"
value={values.orgRegNo}
/>
<Select //imported from antd
id="select"
defaultValue="Choose"
onChange={(select: any) => {
setValues(select);
console.log(select); //shows Vender/Supplier, I want this value to be sent to orgType above
}}
>
{data.orgTypes.map((option: any) => (
<Option key={option.id} value={option.name}>
{option.name}
</Option>
))}
</Select>
</React.Fragment>
);
};
When I use onChange of Select, it erases all other data of the inputs.
Thank you for your help
setValues function expects to take the values object as an argument. Instead you are passing the select value to it.
Instead, you can do it like this:
const handleSelectChange = (selectValue: any) => {
setValues((values: any) => ({
...values,
orgType: selectValue
}));
}
and use handleSelectChange in the onChange of your select.
Check the solution here: https://codesandbox.io/s/react-typescript-p9bjz

Resources