I am working on a form in React that uses a date picker, which uses a ref. However, when multiple of these date pickers are put onto one form, the refs on each datepicker instance seems to refer to the last one defined on the form. For example, choosing a date on the first or second date picker would only update the last one but not itself.
Where I define the ref:
<div className={styles.datePicker} ref={this.setWrapperRef}>
<div className={styles.mdpInput} onClick={()=> this.showDatePicker(true)}>
<input type='date' onChange={this.updateDateFromInput} ref={inputRef}/>
</div>
...rest of code
</div>
Setting the ref:
setWrapperRef(node){
this.wrapperRef = node;
}
I also made sure to bind the function:
constructor(props) {
super(props);
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth();
this.state = {
year,
month,
selectedDay: todayTimestamp,
monthDetails: this.getMonthDetails(year, month)
}
this.setWrapperRef = this.setWrapperRef.bind(this);
}
I just learned about refs and I am fairly new to React, so there might be a simple answer that I am overlooking or unaware of. Thanks!
Related
I have a component which renders x amount of DateInputs based on x number of charges. The date inputs should be rendered with the default value loaded from the server. Once the onChange is triggered it updates the state in the parent and prints out the console with the selected date using the computedProperty. To give more context here is an image of the page:
A you can see, a datepicker is rendered for each charge and the user needs the ability to modify the charge date. So in terms of data flow, the onChange in the child component triggers the method handleChargesDateChange in the parent, as the state needs to be stored in parent.
At the minute, the default charge date is rendered in the dropdown, but when onChange is executed then date input value does not change. However, the method prints out the correct date that was selected. Again, for more context, here is a screenshot of the data stored in parent when child executes onChange on the date picker.
As you can see, the date values get modified when the user selects one from each datepicker. However, the actual datepicker value stays the same. I can get it to work where it renders with an empty date for each datepicker and when the user selects a new date, it will render the datepicker with the selected date. It also stored the value in parent. But I am really struggling to render the defalt value, but when it is changed update the datepicker with the user selected value... I hope this makes sense.. Anyway here's the code. So the method which gets triggered in parent:
handleChargesDateChange = (e) => {
const date = e.target.value;
const momentDate = moment(date);
const chargeId = e.target.name;
console.log(date);
console.log(momentDate);
this.setState({
updatedChargeDates: [
...this.state.updatedChargeDates,
{[chargeId]: date}
]
}, () => console.log(this.state.updatedChargeDates))
}
This then gets passed down (around 3 children deep) via:
<Grid.Row>
<Grid.Column width={16}>
<OrderSummary
order={this.state.order}
fuelTypes={this.state.fuelTypes}
vehicleClasses={this.state.vehicleClasses}
deviceTypes={this.state.deviceTypes}
chargeTypes={this.state.chargeTypes}
featureTypes={this.state.featureTypes}
itemGroups={this.state.itemGroups}
itemTypes={this.state.itemTypes}
updateChargeDates={this.handleChargesDateChange}
chargeDates={this.state.updatedChargeDates}
/>
</Grid.Column>
I will skip the various other children components that the method gets passed to and go straight to the effected component. So the method then lands at Charges:
export class Charges extends Component {
constructor(props) {
super(props);
this.state = {
currentItem: 0,
newDate: '',
fields: {}
}
}
render() {
const {charges, chargeTypes, updateChargeDates, chargeDates} = this.props;
console.log('Props method', charges);
if (charges.length === 0) { return null; }
return <Form>
<h3>Charges</h3>
{charges.map(charge => {
console.log(charge);
const chargeType = chargeTypes.find(type => type.code === charge.type) || {};
return <Grid>
<Grid.Row columns={2}>
<Grid.Column>
<Form.Input
key={charge.id}
label={chargeType.description || charge.type}
value={Number(charge.amount).toFixed(2)}
readOnly
width={6} />
</Grid.Column>
<Grid.Column>
<DateInput
name={charge.id}
selected={this.state.newDate}
***onChange={updateChargeDates}***
***value={chargeDates[charge.id] == null ? charge.effectiveDate : chargeDates[charge.id]}***
/>
</Grid.Column>
</Grid.Row>
</Grid>
})}
<Divider hidden />
<br />
</Form>
}
I have highlighted the culprit code with *** to make it easier to follow. I am using a ternary type operator to determine which value to display, but it doesnt seem to update when i select a value.. I thought that from the code I have, if the initial chargeDates[charge.id] is null then we display the charge.effectiveDate which is what it is doing, but soon as I change the date i would expect chargeDates[charge.id] to have a value, which in turn should display that value...
I really hope that makes sense, im sure im missing the point with this here and it will be a simple fix.. But seems as im a newbie to react, im quite confused..
I have tride to follow the following resources but it doesnt really give me what I want:
Multiple DatePickers in State
Daepicker state
If you need anymore questions or clarification please do let me know!
Looking at your screenshot, updatedChargeDates (chargeDates prop) is an array of objects. The array has indexes 0, 1 and 2, but you access it in the DateInput like this chargeDates[charge.id] your trying to get the data at the index 431780 for example, which returns undefined, and so the condition chargeDates[charge.id] == null ? charge.effectiveDate : chargeDates[charge.id], will always return charge.effectiveDate.
You can fix this in handleChargesDateChange by making updatedChargeDates an object, like this :
handleChargesDateChange = (e) => {
const date = e.target.value;
const momentDate = moment(date);
const chargeId = e.target.name;
console.log(date);
console.log(momentDate);
this.setState({
updatedChargeDates: {
...this.state.updatedChargeDates,
[chargeId]: date
}
}, () => console.log(this.state.updatedChargeDates))
}
I currently have 2 inputs which look like this:
I'm trying to update the end date input based on the first date input. Example if I input 08/15/2018 inside start date input I expect end date input to equal 08/15/2018.
My current code looks like this for end date input:
<Field
component="input"
format={(value, name) => {
if (startDate.length && name === "start_date") {
return startDate;
}
return value;
}}
name="end_date"
onChange={onDateChange}
type="date"
/>
The variable startDate captures the input from start date input
The current code is able to display the date under end date input, however, it is not updating the redux field - it remains undefined.
How can I display the data and also save it in redux form?
import { getFormValues, change } from 'redux-form';
Fetch value of field first
const mapStateToProps = state => ({
formValues: getFormValues('<formname>')(state) || {},
});
Access that field inside above object received. formValues.
You can use change to change value in store.
change(field:String, value:any)
export const mapDispatchToProps = dispatch => ({
setDate: value => dispatch(change('<formName>', value, null)),
});
Is there a sort by date in the React data grid? If so, how can this be called? In the examples, all sorting works only on the line:
Example 1
Example 2
In example 2, there are columns with dates, but they are sorted as strings.
i have a solution that is really good but not great. really good in that it sorts perfectly but it isn't good if you also want to filter on the column as well.
Basically you are displaying the formated date with a custom formatter and the value you are passing to sort on is the the number of seconds from 1/1/1970 for each date so it will sort right.
And yes, i use functions instead of components for the formatter and headerRenderer values all the time and it works fine.
i work an an intranet so i can't just copy/paste my code so this is just hand typed but hopefully you get the idea.
class GridUtil {
static basicTextCell = (props)=>{
return (<span>{props.value? props.value : ""}</span>)
}
}
class AGridWrapper extends React.Component {
///////////////
constructor(props){
super(props);
this.state={...}
this.columnHeaderData = [
{
key: "dateCompleted",
name: "Date Completed",
formatter: (data)=>GridUtil.basicTextCell({value: data.dependentValues.dateCompletedFormatted}),
getRowMetaData: (data)=>(data),
sortable: true
}
];
}//end constructor
///////////////////////////////////
//Note that DateUtil.getDisplayableDate() is our own class to format to MM/DD/YYYY
formatGridData = !props.inData? "Loading..." : props.inData.map(function(obj,index){
return {
dateCompleted: rowData.compDate? new Date(rowData.compDate).getTime() : "";
dateCompletedFormatted: rowData.compDate? DateUtil.getDisplayableDate(rowData.compDate) : "";
}
});
//////////
rowGetter = rowNumber=>formatGridData[rowNumber];
/////////
render(){
return (
<ReactDataGrid
columns={this.columnHeaderData}
rowGetter={this.rowGetter}
rowsCount={this.formatGridData.length}
...
/>
}
I use react-table I was having the same issue, the solution in case, was to convert the date to a javaScript Date, and also return this as a JSon format, I have to format how the date render in the component. here is a link that shows how I did this.
codesandbox sample
I'm building a simple weekly calendar component for my app and I'm struggling to find a way for creating the weeks navigation. Here's what I have so far:
/week-view/component.js
import Ember from 'ember';
export default Ember.Component.extend({
firstDay: moment().startOf('week'),
days: Ember.computed('firstDay', function() {
let firstDay = this.get('firstDay').subtract(1, 'days');
let week = [];
for (var i = 1; i <= 7; i++) {
var day = firstDay.add(1, 'days').format('MM-DD-YYYY');
week.push(day);
}
return week;
}),
actions: {
currentWeek() {
this.set('firstDay', moment().startOf('week'));
},
previousWeek() {
this.set('firstDay', moment().startOf('week').subtract(7, 'days'));
},
nextWeek() {
this.set('firstDay', moment().startOf('week').add(7, 'days'));
}
}
});
/week-view/template.hbs
<button {{action "previousWeek"}}>previous</button>
<button {{action "currentWeek"}}>current week</button>
<button {{action "nextWeek"}}>next</button>
<ul>
{{#each days as |day|}}
<li>{{day}}</li>
{{/each}}
</ul>
At the moment it works to navigate one week before and one after the current week only. Any idea on how to make this work for an unlimited number of weeks is greatly appreciated. Thanks in advance.
I think you shouldn't change your firstDay property while prepairing week array (in the computed function). It overrides momentjs state. Computed property in this case should just read firstDay property without affecting changes to it.
Also in your previous and next week actions you don't have to create new momentjs objects. You can easily operate on previously created firstDay property, for example.
this.get('firstDay').subtract(7, 'days');
But in this case, state of momentjs have changed, but emberjs doesn't see any changes. That is because your firstDay property doesn't really changed (and computed property is set to check only firstDay, it doesn't work deeply). In fact firstDay property is just reference to momentjs object, and that object has been changed, not the reference. But luckily you can manually force emberjs to reload any computed properties based on any property in this way:
this.notifyPropertyChange('firstDay');
So little bit refactored working example can looks like:
import Ember from 'ember';
export default Ember.Component.extend({
selectedWeek: moment().startOf('week'),
days: Ember.computed('selectedWeek', function() {
let printDay = this.get('selectedWeek').clone();
let week = [];
for (var i = 1; i <= 7; i++) {
week.push(printDay.format('MM-DD-YYYY'));
printDay.add(1, 'days');
}
return week;
}),
actions: {
currentWeek() {
this.set('selectedWeek', moment().startOf('week'));
},
previousWeek() {
this.get('selectedWeek').subtract(7, 'days');
this.notifyPropertyChange('selectedWeek');
},
nextWeek() {
this.get('selectedWeek').add(7, 'days');
this.notifyPropertyChange('selectedWeek');
}
}
});
See this gist for the complete picture.
Basically I will have this form:
When you click the plus, another row should appear with a drop down for day and a time field.
I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.
The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....
I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.
Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...
When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.
Using State Correctly
There are three things you should know about
setState().
Do Not Modify State Directly
For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
The only place where you
can assign this.state is the constructor.
read more from Facebook directly here
I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:
suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):
inputs = {
1: {
selectedTime: 0:00,
selectedValue: 2
}
}
in your render, do something like this:
render() {
let inputs = Object.keys(this.state.inputs).map((key) => {
let input = this.state.inputs[key]
return (<SelectTimeInput
key={key}
name={'option_' + key}
placeholder={this.props.placeholder}
options={this.props.options}
onChange={this.onChange.bind(this, key)}
timeValue={input.selectedTime}
selectValue={input.selectedValue}
/>)
)}
return (
<div>
<button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>
{ inputs }
</div>
);
}
Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:
onChange(event, key) {
this.setState({
inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()
// or
inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))
})
}
this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.
again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.
does this make sense?
I modified the onChange in the MultipleDayTimeInput:
onChange(event) {
const comparisonKey = event.target.name.substring(event.target.name.length - 1);
const input = this.getInputState(comparisonKey);
input.selected = event.target.value;
input.display = this.renderTimeInput(input);
let spliceIndex = -1;
for (let i = 0; i < this.state.inputs.length; i++) {
const matches = inputFilter(comparisonKey)(this.state.inputs[i]);
if (matches) {
spliceIndex = i;
break;
}
}
if (spliceIndex < 0) {
throw 'error updating inputs';
}
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
}
The key points are:
// re render the input
input.display = this.renderTimeInput(input);
// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??