react date picker multi range with react hook form - reactjs

The reason we use react hook form is that it decreases our state count and increases performance. But I didn't know how to do it when using Date range for one datepicker.
How to keep two data in one controller?
`() => {
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(null);
const onChange = (dates) => {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
};
return (
<DatePicker
selected={startDate}
onChange={onChange}
startDate={startDate}
endDate={endDate}
selectsRange
inline
/>
);
};`
If this piece of code is my code, I can only capture one value with selected, but I need to return 2 values. How can I use this in the best way with the react hook form?
<Controller
name="orderDate"
control={control}
render={({ field }) => (
<DatePicker
selected={field.value}
onChange={(date) => field.onChange(date)}
selectsRange
/>
)}
/>

I've got the exact same issue last week, here is the solution...
You just need to pass (at least) the value to the "Controller function", you achieve that using only the field object.
In any case, When the DatePicker component is used for a date range, it will require a two-position vector data type, where the start and end dates are stored as a string. But if you try to manipulate and control this component via the onChange function that comes from the reack-hook-form it's gonna screw up the entire thing, because is made for a one-to-one data flux. Then the processes are done separately but the vector is still sent to the controller. Here is the code!
const [dateRange, setDateRange] = useState([null, null]);
const [startDate, endDate] = dateRange;
<Controller
//is a prop that we get back from the useForm Hook and pass into the input.
control={control}
//is how React Hook Form tracks the value of an input internally.
name={name}
//render is the most important prop; we pass a render function here.
render={({
//The function has three keys: field , fieldState, and formState.
field, // The field object exports two things (among others): value and onChange
}) => (
<>
<DatePicker
selectsRange={true}
startDate={startDate}
endDate={endDate}
onChange={(e) => {
setDateRange(e);
field.onChange(e);
}}
isClearable={true}
className="form-control"
/>
</>
)}
rules={{
required: `The ${label} field is required`,
}}
/>

Related

React-Hook-Form Controller ignoring useEffect

I have two components, where selectBirthMonth depends on SelectBirthYear. Which is why I use useEffect, to watch the selectedYear change, so that I can change selectedMonth in case it is needed.
code sandbox
So in controller context my components look the following
<Controller
control={control}
name="selectedBirthYear"
defaultValue={years[0]}
render={({ field }) => (
<SelectBirthYear
field={field}
years={years}
value={selectedYear}
defaultValue={selectedYear}
onChange={useEffect(() => {setSelectedYear(field.value)})}
/>
)}
/>
</div>
and ...
<Controller
control={control}
name="selectedBirthMonth"
defaultValue={months[0]}
render={({ field }) => (
<SelectBirthMonth
field={field}
startYear={startYear}
selectedYear={selectedYear}
months={months}
value={selectedMonth}
defaultValue={selectedMonth}
reducedMonths={reducedMonths}
onChange={useEffect(() => setSelectedMonth(field.value))}
/>
)}
/>
SelectBirthMonth totally ignored the following code though:
const [selectedMonth, setSelectedMonth] = useState(months[0]);
const watchYearChange = () => {
if(Number(selectedYear.name) == startYear){
setSelectedMonth(reducedMonths[reducedMonths.length - 1]);
}
};
useEffect(() => watchYearChange(), [selectedYear]);
Meaning, no matter, which year is chosen, the month won't react. What do I miss?
I would recommend using a small date library like date-fns to handle date related things. It's a great package, here are the docs for it. Also it can handle i18n for you if this should be a requirement in the future.
I used it in the below CodeSandbox and also corrected a few things:
when you use RHF you don't need to setup extra state for your component as RHF will manage the form state for you.
it's much simpler if you just use one date for the whole birthdate - this way you will always have the current values for year, month and day and don't need to watch values or use extra defined state
The answer is way too easy, to be working, but it does. Several times I've been reading this post here How to change React-Hook-Form defaultValue with useEffect()?, but could't really understand, where and how do I use setValue. As I assumed, the value of my select just wasn't changing, even though I was watching the sibling state change.
So I put the setValue into the useEffect hook and the rest stayed the same:
const monthSelect = (data) => {
setSelectedMonth(months[data.id - 1]);
};
const watchYearChange = () => {
if(Number(selectedYear.name) == startYear){
setSelectedMonth(lastAvaliableMonth)
}
};
useEffect(() => setValue('selectedBirthMonth', lastAvaliableMonth), [selectedYear]);
Here are two siblings just in case:
<Controller
control={control}
name="selectedBirthYear"
defaultValue={years[0]}
render={({ field }) => (
<SelectBirthYear
field={field}
years={years}
value={selectedYear}
defaultValue={selectedYear}
onChange={useEffect(() => {setSelectedYear(field.value)})}
/>
)}
/>
... and
<Controller
control={control}
name="selectedBirthMonth"
defaultValue={selectedMonth}
render={({ field }) => (
< SelectBirthMonth
field={field}
startYear={startYear}
selectedYear={selectedYear}
months={months}
value={selectedMonth}
reducedMonths={reducedMonths}
onChange={useEffect(() => monthSelect(field.value)), [selectedMonth]}/>
)}
/>
If this solution somehow is not good, please let me know in the comments. I am a total beginner.

React useState pattern for internal and external changes (to avoid stale state)

I have a few form components that for performance reasons, only dispatch changes to their values (to their parent component) when they blur. The naive implementation is as follows:
function TextField (props)
{
const [value, set_value] = useState(props.value)
return <input
type="text"
value={value}
onChange={e => set_value(e.currentTarget.value)}
onBlur={() => props.on_blur(value)}
/>
}
This is ok until another form component alters the value represented by this component. For example, setting a value on another field when this field is empty causes the other component to also set a default value of this field based on the other field. In this case however this form component does not update as it has the original props.value in its useState.
I would like there to be a way to tell the useState to update its value when it is changed from its parent.
Currently I am using the following pattern but I curious if there is a better / more elegant way of doing this:
function TextField (props)
{
const [value, set_value] = useState(props.value)
useEffect(() => set_value(props.value), [props.value]) // <---
return <input
type="text"
value={value}
onChange={e => set_value(e.currentTarget.value)}
onBlur={() => props.on_blur(value)}
/>
}

Function components cannot be given refs. Attempts to access this ref will fail in select component

Here I defined a select component, and I wanted to display it if a condition is true. This select field appears when one of the values ​​of the previous select input is selected. But here is when the new select field (i.e. my select component), and I choose a value from this select, this value is not submitted by my form, and is not present in my data when I do a console log after submitting my form, but the name of the value field is there, but not the value. And i have a warning in my console stating:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of Controller
My Select Component
export const SelectCompanyField = () => {
// const [page, setPage] = useState(1);
// const [perPage, setPerPage] = useState(10);
const { data, error } = useSWR<ServerResponse, any>(companyUrls.base, url => getDataByParams(companyUrls.base));
console.log("Data", data, "Error", error);
console.log(data?.entities);
return (
<Select
showSearch
placeholder="Choisir une compagnie"
optionFilterProp="children"
filterOption={(input, option: any) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{data?.entities.map(d => (
<option value={d.index} key={d.id} >
{d.name}
</option>
))}
</Select>
);
};
The select component to display if the condition is true
<Col md={24} lg={24} sm={24}>
{firstOptionValue &&
<div className="ant-form-item">
<label className="label">Compagnie <span className="text-danger">*</span></label>
<Controller
as={<SelectCompanyField />}
name="company"
control={control}
defaultValue={""}
rules={{ required: false }}
{errors.company && "Company is required"}
</div>
}
</Col>
The console log of my data submit
{
"firstName": "Atom",
"lastName": "Proton",
"username": "xyz#test.ml",
"password": "00789",
"phoneNumber": "44258569",
"profile": "ADMIN",
"userType": "B2B_CLIENT",
"company": ""
}
The company part with the empty quotes, is the part where it should have the value of the field I chose in my select component.
I would just like the selected value of the field or the option, appear in my data so that I can submit my form.
Thanks
SelectCompanyField needs to be:
export const SelectCompanyField = React.forwardRef(() => {
...
});
When rendering using Controller's as prop, it passes a ref prop to the as prop. React see's a ref being passed, but you aren't using a forwardRef component.
In addition to this, then you need to actually use the ref (and props) which you don't appear to be doing now in SelectCompanyField that are being provided by Controller
The docs will help you out
☝️ Please read the docs, but your SelectCompanyField receives props like this:
export const SelectCompanyField = React.forwardRef(({field,meta}) => {
const { onChange, onBlur, value, ref } = field
return <Select onChange={onChange} value={value} .../>
});
It Is your job to add the onChange handler and the value to the Select component you are rendering. I don't know what Select component it is - is it frame a component framework? is it just a regular select? - so I can't tell you how to do it, but the hook form docs are pretty clear.
For anyone with this problem and a component that you can't use ref (because you haven't created the component can't change it, or it doesn't need a ref prop, or you are using typescript with a generic component and used a different/custom name for the ref prop), you can use the react-hook-mask Controller render prop (https://stackoverflow.com/a/69671594/4850646), instead of as, which allows you to customize which props are passed:
Instead of:
<Controller as={<SelectCompanyField />} ... />
You can use:
<Controller render={({ field: { ref, ...field } }) => <SelectCompanyField {...field} />} ... />

react-hook-form Controller with react-draft-wysiwyg

I need some help. I was using react-final-form in my project but I've decided to change for react-hook-form. By the way, I love it, but I got stuck. :/
On my page, I'm using an editor to collect some info from a user. At the moment I'm using react-draft-wysiwyg.
Here the code:
parentComponent.js
/**
* react-hook-form library
*/
const {
register,
handleSubmit,
control
} = useForm({
mode: 'onChange'
});
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={<WYSIWYGEditor />}
name='editor'
control={control}
onChange={([ event ]) => event.target.value}
/>
</form>
WYSIWYGEditor.js
const WYSIWYGEditor = ({ input }) => {
const [ editorState, setEditorState ] = useState(EditorState.createEmpty());
const onEditorStateChange = editorState => {
setEditorState(editorState);
};
return (
<React.Fragment>
<div className={classes.root}>
<Editor
editorState={editorState}
wrapperClassName='wrapper-class'
editorClassName='editor-class'
onEditorStateChange={onEditorStateChange}
/>
</div>
</React.Fragment>
);
};
export default WYSIWYGEditor;
PLEASE NOTE:
The input prop comes from the coding with react-final-form. The input was passing the characters I was typing. So, if I leave input as props it fails because it doesn't exist. React-hook-form doesn't pass an input.
I've changed that with props:
const WYSIWYGEditor = props=> {
console.log(props)
and I get the following in the console.log when I type anything:
{name: "editor", value: undefined, onChange: ƒ}
As you can see, value is undefined. How can I structure the Controller in order to pass a value each time I type something in the editor?
Thanks for your help
I found a solution.
value is undefined because obviously on component load there is nothin' to load. If you don't want to see undefined just pass defaultValue='' from the controller:
<Controller
as={<WYSIWYGEditor />}
name='editor'
control={control}
onChange={([ event ]) => event.target.value}
defaultValue='' <== here
/>
Now, the issue that doesn't allow to return any typed value, is because I have declared the onChange from the controller. So, the right code would be:
<Controller
as={<WYSIWYGEditor />}
name='editor'
control={control}
defaultValue=''
/>
Also, in the WYSIWYGEditor.js file, you need to replace what before was input with props. The reason is that they are passing exactly the same Object, which contains a value, onChange, and onBlur function.
Here a code sandbox with the working code:
https://codesandbox.io/s/trusting-mountain-k24ys?file=/src/App.js

react | redux-form | material-ui | how to combine DatePicker with my form

After solving some problems, I stuck with sending DatePicker data to my form. In my form I use mostly elements from redux-form-material-ui, but DatePicker is not a part of it.
I found 2 ways of creating DatePicker component with redux-form.
<Field
name="startDate"
autoOk={true}
floatingLabelText="startDate"
component={(startDate) => {
return <DatePicker {...startDate} />;
}}
onChange={(event, date) => {console.log(date);}}
/>
and
<DatePicker
name="startDate"
autoOk={true}
floatingLabelText="startDate"
onChange={(event, date) => {console.log(date)}} />
The problem is that I don't know the way to update the form data with it. The first example even doesn't show the picked date in text field. I can see in form.myForm store, that I made date field active, but it is never updated with picked date. The second shows picked date, but it is not a part of form.myForm object...
I found some examples in internet (e.g. https://github.com/erikras/redux-form/issues/364 ) but there is no fields object in props, so cannot call this.props.fields.startDate.onChange.
I'm not sure what is a good way of working with redux-form and material-ui, as there is not many working examples. I started to think of making a wrapper to EVERY component I use, which will provide onChange method, which will update my store with any change made in form. But then I don't need redux-form anymore, so I think there must some other solution, I could use.
"react": "15.1.0",
"react-tap-event-plugin": "1.0.0",
"redux": "3.0.5",
"redux-form": "^6.0.0-alpha.4",
"redux-form-material-ui": "^2.0.0",
import React from 'react';
import DatePicker from 'material-ui/DatePicker';
export default ({ input, label, meta: { touched, error }, ...custom }) => {
return (
<DatePicker
onChange={(e, val) => {return input.onChange(val)}}
{...custom}
value={input.value}
/>
);
}
Considering this as in file renderDatePicker.js, usage would be,
<Field name="created_on" component={RenderDatePicker} floatingLabelText="Created Date"/>
Where Field is imported from redux-form.
Remember to provide {created_on: new Date('someiso8601')} in this.props.initialize to autopopulate it.
I used react-datepicker with redux form. The key was to set "selected" property correctly.
const selectedDate = this.props.fields.date.value ?
moment(this.props.fields.date.value, 'DD/MM/YYYY') :
moment();
<DatePicker
{...date}
className="form-control"
dateFormat="DD/MM/YYYY"
selected={selectedDate}
/>
Here is how I use it in my code.
<DatePicker
selected={dateField.value}
onChange={param => dateField.onChange(param)} />
dateField simply comes from:
const {fields: {dateField}, handleSubmit} = this.props;
Not just for this Datepicker but for any custom input, create a separate component to manage the input type and process. Which makes easy to feed your redux-form fields. This is the simple process I was taught by my senior developers when I was a Grad.
In your case, firstly import following additional libraries to your redux-form component
import moment from 'moment'
import { change } from 'redux-form' //We use the change to dispatch the own props of the redux-form component to resetDateField
Create a separate component by importing DatePicker in it. And your component will look something like this:
const datePickerInput = ({input,name,label,...props}) => {
return (
<div>
<label htmlFor={input.name}>
<span>
{label}
</span>
</label>
<div>
<DatePicker
className='form-control'
{...input}
{...props}
/>
</div>
</div>
)
}
Now feed this component to your redux-form field's component.
<Field
name = "startDate"
label = "Start Date"
dateFormat = 'MMM Do YY'
component={datePickerInput}
selected = {/*pass state value of startDate or pass some default null for startDate if nothing selected*/}
onChange={ (date) => {this.handleChangeStartDate(date, 'startDate')} } //bind this function in constructor and also setState value for your start date as null or some default
/>
the onChange function is to be handled something like this using moment:
handleChangeStartDate(date, target) {
const {resetDateField } = this.props
this.setState({
[target]: moment(date)
})
resetDateField('startDate', '' )
}
Now hook this resetDateField in your reduxForm #connect by passing dispatch and ownProps and returning resetDateField value expecting a dispatch of change for current form, field, and field value that you want to change using this resetDateField.

Resources