I recently upgraded to redux-form v6, and my material-ui custom datetime picker stopped working. Here's what it looks like now:
class DateTimePicker extends React.Component {
handleDateChanged(e, value) {
let currentTime;
if (this.props.dateField.value) {
let currentValue = moment(this.props.dateField.value);
currentTime = currentValue.subtract(currentValue.clone().startOf('day'));
}
let newValue = moment(value).startOf('day').add(currentTime || 0);
this.props.dateField.onChange(newValue.toDate()); //this.refs.timePicker goes undefined when this line runs.
this.refs.timePicker.openDialog();
}
handleTimeChanged(e, value) {
return this.props.dateField.onChange(value); //This is called, but the value does not change.
}
formatDate(value) {
return moment.tz(value, this.props.timezone).format('M/D/YY HH:mm zz');
}
render() {
return (
<span>
<DatePicker
value={this.props.dateField.value || null}
autoOk={true}
maxDate={new Date()}
floatingLabelText="Change At"
floatingLabelStyle={{pointerEvents: 'none'}}
errorText={this.props.dateField.touched && this.props.dateField.error}
formatDate={this.formatDate.bind(this)}
onChange={this.handleDateChanged.bind(this)}
/>
<TimePicker
style={{display: 'none'}}
value={this.props.dateField.value || null}
format="24hr"
hintText="Time"
ref="timePicker"
onChange={this.handleTimeChanged.bind(this)}
/>
</span>
);
}
}
And it's used in the form like so:
<Field
name="changeAt"
component={({input}) => {
return <DateTimePicker dateField={input} timezone={this.props.driver.homeTerminal.timezone} />
}}
/>
I'm having two distinct problems.
First problem: When I pick a day, handleDateChanged is called as expected. When the method starts, this.refs.timePicker refers to the time picker as expected. However, when the line above it is executed (this.props.dateField.onChange(newValue.toDate())), then refs are lost and and this.refs.timePicker changes to undefined.
Each subsequent time time I pick a date, refs are not lost, and it works correctly.
Second problem: When I pick a time, handleTimeChanged is called which calls this.props.dateField.onChange(value) with the correct value; however the value is not changed.
Any suggestions are appreciated.
I'm new to this stuff so I don't know whether I'll be much use. But addressing the second problem: The only difference between my code and yours is that I'm doing<DatePicker ... onChange={this.props.onChange} />. Alternatively you could do
constructor(){this.handleChange = this.handleChange.bind(this)}
and then remove the bind in the render() method. The render method gets called every time the (props?) change and perhaps the issue is to do with this bind.
The docs on this: http://redux-form.com/6.0.0-rc.4/docs/faq/CustomComponent.md/
I'm also using a react element instead of using a component, and it comes out quite cleanly.
const DateInput = ({ input, label, meta: { touched, error } }) => (
<DateTimeField onChange={input.onChange} />
);
with
<Field name="date" component={DateInput} label="Date & Time Starting" />
The above is working, so perhaps you could try iterating from there and adding code until the problem is reproduced?
Answer right at the bottom and work your way up to suit various implementations:
https://github.com/erikras/redux-form-material-ui/issues/37
It's wanting an object, more specifically a Date object.
format={(value) => value === '' ? null : new Date(value)}
Related
I need simple functionality: show Button when value is more than 0. And for this I use code below.
I created some Text Fields with similar states (4) and I don't understand why only in 3rd this didn't work.
My code:
export default function TextFields() {
...
const [showButton3, setShowButton3] = useState("");
...
const handleChange = (event) => {
setShowButton3(event.target.value);
console.log("value is:", event.target.value);
};
return (
<InputOutlined
type={"text"}
id={"text3"}
name={"text3"}
value={showButton3}
onChange={handleChange}
leftElement={
<Img
width={36}
height={36}
radius={12}
src={
"https://images.unsplash.com/photo-1665174271625-178021f8b1a5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1160&q=80"
}
/>
}
rightElement={
showButton3 ? (
<IconButton
icon={<MdClose size={24} />}
variant={"surface"}
type="reset"
onClick={() => setShowButton3("")}
></IconButton>
) : null
}
>
Your name
</InputOutlined>
)}
I have checked Component, here I add some text
And when I want to clear value, I get this
You can see, value is cleared. But I still see it in my input. How to fix that? Or maybe I doing something wrong?
Proof:
This is very similar Components. I changed id, but I don't understand why value isn't removed. Maybe I need to use useRef or useId. But I have 4 different inputs and only 1 have this issue.
We are using antd for datepicker and moments as util. I'm stuck for a week in this ISSUE.The thing is, in the filter sidepanel,on pressing clear,all the fields should clear or set to their default values(in case of dropdown).But the date picket is not resetting.
The above picture is the Activity component and left side to its his the filter.A basic filter with API change from backend on every action event.
useEffect(()=>{
if(clearFilter){
form.resetFields()
setActivitySearchText('')
setFromDate('')
setToDate('')
setStatusSearchText('')
onStatusChange('')
setClearFilter(false)
}
},[clearFilter])
const onChangeFromDate = dateString => {
setFromDate(new Date(dateString).toISOString())
}
const onPageToDate = dateString => {
setToDate(new Date(dateString).toISOString())
}
<StyledDatePicker
allowClear={false}
format={dateFormat}
disabledDate={disabledFromDate}
placeholder={'From'}
onChange={(fromdate, dateString) =>
onChangeFromDate(fromdate, dateString)
}
showTime={{
use12Hours: true,
defaultValue: moment('00:00:00', 'HH:mm:ss'),
}}
/>
<StyledDatePicker
format={dateFormat}
disabledDate={disabledToDate}
placeholder={'To'}
onChange={(todate, dateString) => onPageToDate(todate, dateString)}
showTime={{ use12Hours: true }}
/>
The above code is the index file for all the components,we'll be passing clearfilter prop,if its true ,the filter components are set to empty.The StyledDatePicker is just wrapped in styled components of some custom width.that's it.
You can clearly see,onChangeFromDate() and onPageToDate() are the event functions happening on Change,onChange. As I said above,I'm setting the setFromDate('') and setTodate('') when clearFilter is true.
To give some context,this another main file,from which the props are passed to the others.In there,we are defining setFromDate('') and setTodate('') as,
const [fromDate, setFromDate] = useState('')
const [toDate, setToDate] = useState('')
I think I've given enough details. If need anything, request, I'm ready to give. This is a live project, I'm stuck for a week.Thanks in advance!
I have implemented the datepicker and timepicker with add,delete buttons in each row. When I click on add, will add new row and delete will delete row.
I have the code link https://codesandbox.io/s/zen-water-tfyoz?fontsize=14&hidenavigation=1&theme=dark
But how to handle the state for multiple datepicker and timepicker,
When change the date, it doesnot reflect the change in field.
https://codesandbox.io/s/zen-water-tfyoz?fontsize=14&hidenavigation=1&theme=dark
So the problem is in your renderRowData function:
<td key={`tableview-td-${rowId}-${index}`}>
{column.dataFieldId === "pickdate" ? (
<DatePicker
locale="en-GB"
className="datepicker"
name={"pickdate_" + rowId}
onChange={e =>
this.handleDatePicker(
e,
"pickdate_" + rowId,
column.dataFieldId,
row
)
}
value={this.state.pickdate}
/>
)
For value u use this.state.pickdate, but when value changes you set it with:
handleDatePicker = (value, name, field, row) => {
this.props.handleInputChange(value, field, row);
console.log("data", value, "for", name);
this.setState({ [name]: value });
};
wich means that your state is now:
{
["pickdate_" + rowId]: value // where row is selected row
}
you need to change your datepicker to access value like this:
<td key={`tableview-td-${rowId}-${index}`}>
{column.dataFieldId === "pickdate" ? (
<DatePicker
locale="en-GB"
className="datepicker"
name={"pickdate_" + rowId}
onChange={e =>
this.handleDatePicker(
e,
"pickdate_" + rowId,
column.dataFieldId,
row
)
}
value={this.state["pickdate_" + rowId] || this.defaultPickDate} // this will take new selected value or default if there is none
/>
)
Working example: https://codesandbox.io/s/funny-fog-1v9o3
The reason that the date field's changes aren't reflected in the UI is that you've implemented it as a controlled component (you are setting the value of each DatePicker based on the corresponding value in the state and updating the state when you change the value, which effectively synchronizes the component with the state, more or less), whereas the TimePickers do change in the UI when a new time is chosen because they are implemented as uncontrolled components. Controlled components are often the best method, but your date component isn't updating on change because there are problems in your handleDatePicker function, as pointed out by Kaca992's answer.
If you don't know much about controlled vs uncontrolled components, see here.
As for how to handle the state for multiple DatePickers and TimePickers, personally I'd recommend that you store them as an array of rows in the state. For example, this would be a default state:
this.state = {
rows: [
{
date: new Date(),
start: moment(),
end: moment(),
}
]
}
Each row element in the array would correspond to a row in the table, and you can just use array.map to render each row as a component that contains the DatePicker, TimePickers, and buttons, and then just send the array index along with the new value to your onChange functions so that the correct row's changes can be reflected in the new state.
This would require a bit of a re-write of your DynamicDateTimePicker class, but the logic would be much simpler and more readable than how you currently have it structured.
This is my Creatable component:
function optionsForSelect(field) {
return field
.values
.map((fieldOption) => {
return {value: fieldOption, label: fieldOption};
});
}
function PatientSelectInput({field, options, value, onChange, disabled}) {
const className = field.id + '-select';
return (
<label className={cx('input-label', className)}>
<div className="label-text">{field.displayName}</div>
<Creatable
value={value}
onChange={(selectedValue) => onChange(selectedValue ? selectedValue.value : null)}
disabled={disabled}
onBlurResetsInput={false}
onCloseResetsInput={false}
options={options} />
</label>
);
}
It is a functional component. When it renders, I can create a new option but when I hit tab or enter or click on the automatically generated "Create Option..." the newly created option disappears. I just want the default behavior.
What am I missing?
Unfortunately this version of react-select mutates it's options prop.
This is very bad coding and is fixed in version 2: https://github.com/JedWatson/react-select/issues/2484
The issue you're experiencing is coming from the way you construct the options. Your function creates a new object on each render, replacing the array containing the option you just created.
If you can't update your version I suggest you save the output of optionsForSelect to this.state.options, then pass the state variable into react-select. The upshot of doing this is you still have the ability to mutate the state of your element and limits the impact of the mutate.
I have a Materialize input like so:
<input type="date" className="datepicker" onChange={this.props.handleChange} />
It is being correctly initialised by Materialize, but not firing onChange when the value of the datepicker changes. What am I doing wrong here? This problem seems to extend to all Materialize inputs.
On componentDidUpdate() using a prop id
var elem = document.getElementById('date');
M.Datepicker.init(elem,{
onClose:()=>{
this.state.date = elem.value;
this.setState(this.state)
}
});
I'm pretty sure this solves the caveat if you put it in your componentDidMount component.
If the select is to be re-rendered on state change, this should as well be put in componentDidUpdate
// find the select element by its ref
const el = ReactDOM.findDOMNode(this.refs.ref_to_my_select);
// initialize the select
$('select').material_select();
// register a method to fireup whenever the select changes
$(el).on('change', this.handleInputChange)
To get the value of the datepicker in materialize they provide an onSelect option when initialising the component:
var instances = M.Datepicker.init(
elems,
{
onSelect:function(){
date = instances[0].date;
console.log(date);
}
}
);
https://codepen.io/doughballs/pen/dyopgpa
Every time you pick a date, onSelect fires, in this case console.logging the chosen date.
When you close the datepicker (which is actually a modal), that's when the onChange fires, in this case logging 'onChange triggered' to the console.
that's my solution. I use useRef hook, to identify datepicker input and when onClose is fired, we can capture the object and data value, through ref var.
import React, { useEffect, useState, useRef } from "react";
import M from "materialize-css";
export default function App() {
const fromref = useRef(null); //create reference
const [date, setDate] = useState({ fromdate: "" });
const { fromdate } = date;
useEffect(() => {
let elem = document.querySelectorAll(".datepicker");
M.Datepicker.init(elem, {
firstDay: true,
format: "yyyy-mm-dd",
onClose: function() { // when onclose datepicker, we can get the value and data through the ref
console.log(fromref.current.name, fromref.current.value);
setDate({ [fromref.current.name]: fromref.current.value });
}
});
}, []);
return (
<form class="col s12">
<div class="row">
<div class="input-field col s12">
<input
name="fromdate"
type="text"
class="datepicker"
placeholder="from date"
ref={fromref} //set reference to the input
/>
</div>
</div>
</form>
);
}
If you want to get the value or other attributes you can access them from instaces variable when initialized and then check before submitting your form.
var elems = document.querySelectorAll('.timepicker');
var instances = M.Timepicker.init(elems);
Then in order to get your value before submitting your form can do as follow:
var date = instances[0].el.value;
There are two things which might be stopping the execution of expected behaviour.
If the code which you have displayed question section is from
rendered html tree, then onchnage assigment needs to be called while
assignment itself.
<input type="date" className="datepicker" onChange=this.props.handleChange(event)/>
Note: Previously browser events use to expects event callback
handlers in string format as a value.
In MaterializeCss documentation there is no mentioning of onChange event, this means there cannot be direct way to get it.
https://materializecss.com/pickers.html
It looks like you're using materialize directly in your post but if it is possible, you could try using react-materialize as it wraps all the materialize components such that it's easier to use with React. Using react-materialize would probably be the cleanest way to handle state and event changes as they provide a convenience wrapper around each materialize component.
When using the date picker from react-materialize, you'll need to pass the handleChange method into the options prop like so:
<DatePicker
options={{
...,
onSelect: this.props.handleChange
}}
/>
In the case of using the materialize date picker independently, if you could provide more details on how you're initializing the date picker input, I could provide a more relevant answer. But I'll give it a shot in the dark.
From the materialize docs it looks like you'll also have to pass back some options when you initialize it to handle a callback function when a date is selected.
I've added a JSFiddle that has a working example as well as a code snippet below, notice that when you select a date, 'hello world' is logged in the console, and the date is the first argument passed into the callback.
class Datepicker extends React.Component {
constructor(props) {
super(props)
}
handleChange(date) {
console.log('hello world', date);
}
componentDidMount() {
var elems = document.querySelectorAll('.datepicker');
var instances = M.Datepicker.init(elems, {
onSelect: this.handleChange
});
}
render() {
return (
<div>
<input type="text" className="datepicker" />
</div>
)
}
}
Live Example Fiddle
So to answer your question of how to handle events and setting the state, you just need to pass your handleChange method into the provided options configs depending on how you're using materialize date picker. In regards to integrating with a form, you could use the other callback hooks like onClose to do form validation.