anobilisgorse anobilisgorse - 1 month ago 8
React JSX Question

Auto adjust dependent state and vice versa when changing

So I am using Airbnb's react-dates library for a calendar:

Component



export const Calendar = ({setDates, startDate, endDate, setFocused, focusedInput}) => {
return (
<div className="input-group">
<div className="input-group_addon">
<i className="icon-date-inactive" aria-hidden="true"></i>
</div>
<DateRangePicker
startDate={startDate}
endDate={endDate}
focusedInput={focusedInput}
displayFormat="ddd, D MMM"
onDatesChange={setDates}
onFocusChange={setFocused}
/>
</div>
)
}


Container



const mapStateToProps = (state) => {
return {
startDate: state.model.model.calendar.startDate,
endDate: state.model.model.calendar.endDate,
focusedInput: state.model.model.calendar.focusedInput
}
}

const mapDispatchToProps = (dispatch) => {
return {
setDates: (dates) => {
dispatch(marketplaceSetDates(dates.startDate, dates.endDate));
},
setFocused: (focusedInput) => {
dispatch(marketplaceSetFocused(focusedInput));
}
}
}

export const CalendarContainer = connect(mapStateToProps, mapDispatchToProps)(Calendar);


Actions



export function marketplaceSetDates(startDate, endDate) {
return {
type: 'MARKETPLACE_MODEL_DATES_CHANGE',
dates: {
start: startDate,
end: endDate
}
}
}

export function marketplaceSetFocused(focusedInput) {
return {
type: 'MARKETPLACE_MODEL_FOCUS_CHANGE',
focusedInput: focusedInput
}
}


Reducer



let initialState = {
model: {
calendar: {
startDate: moment().add(1, 'day'),
endDate: moment().add(4, 'day'),
focusedInput: null
}
},
}

export const modelReducer = (state = initialState, action) => {
let newState = {};
switch(action.type) {
case MARKETPLACE_MODEL_FOCUS_CHANGE:
newState = Object.assign({}, state);
newState.model.calendar.focusedInput = action.focusedInput;
return newState;

case 'MARKETPLACE_MODEL_DATES_CHANGE':
newState = Object.assign({}, state);

newState.model.calendar.startDate = (action.dates.start === null) ?
state.model.calendar.startDate : action.dates.start;

newState.model.calendar.endDate = (action.dates.end === null) ?
state.model.calendar.startDate.add(4, 'day') : action.dates.end;

return newState;
}
return state;
}


The one rule for our calendar is that, whenever the user select a new
startDate
or
endDate
, it must automatically adjust to be at least 3 days apart.

For example as you could see, the initial state sets the dates to be tomorrow and 4 days from now.


  • So it would be Oct 19 -> Oct 22.

  • If I click for a new
    startDate
    on Oct 24, (NOTE THAT IT IS NOT WITHIN THE RANGE OF THE PREVIOUS DAYS), thus the
    endDate
    must automatically adjust to Oct 27.

  • Same goes when clicking on an
    endDate
    before the selected range, the
    startDate
    must automatically adjust accordingly, 3 days before the selected
    endDate
    .



But with my current code, when I click on the
startDate
, both
startDate
and
endDate
is set to 3 days after
, which should only be
endDate
's value.

I suspect the reason behind this is due to mutating the state in
redux
is asynchronous, thus the
state.model.calendar.startDate.add(4, 'day')
goes first before the
newState.model.calendar.startDate = ...
, am I correct in my assumption?

Otherwise, I would like help for a workaround.

Answer

TL;DR: Change the add days function to: state.model.calendar.startDate.clone().add(4, 'day')

I believe the issue is with the momentjs add function. From the docs: http://momentjs.com/docs/#/manipulating/add/

Add

... Mutates the original moment by adding time.

This means that when you do state.model.calendar.startDate.add(4, 'day') it actually changes the value of state.model.calendar.startDate.

So ideally, you should copy the moment and then change it using clone()