Can't submit react form - reactjs

I have a method to submit the edited form, but when I'm clicking submit button, nothing happens. Via the console I figured out, that eventIndex = -1. What should I fix in the code below?
editEvent(event) {
const { currentUser, editGoogleCalendarEvent, calendarEvents } = this.props;
const {
events } = this.state;
let onlyDate = false;
console.log('event', event);
console.log('calendarEvents', calendarEvents);
const idx = events.indexOf(event);
const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
const editedEvent = { ...event };
console.log('Event Index', eventIndex);
const nextEvents = [...events];
nextEvents.splice(idx, 1, editedEvent);
if (eventIndex !== -1) {
const item = calendarEvents.details.items[eventIndex];
if (item.start.date && item.end.date) {
editedEvent.start = moment(event.start).format('YYYY-MM-DD');
editedEvent.end = moment(event.end).format('YYYY-MM-DD');
onlyDate = true;
}
}
this.setState({
events: nextEvents,
}, () => {
console.log('Object', { id: event.event.id, title: event.formValues.title, userId: currentUser.id, timezone: currentUser.timezone, onlyDate });
editGoogleCalendarEvent({
id: event.event.id,
start: moment(event.event.start).hours(event.formValues.period === 'AM' ? event.formValues.hour % 12 : (event.formValues.hour % 12) + 12).minutes(event.formValues.minute).toISOString(),
end: moment(event.event.end).hours(event.formValues.period === 'AM' ? event.formValues.hour % 12 : (event.formValues.hour % 12) + 12).minutes(event.formValues.minute).toISOString(),
title: event.formValues.title,
userId: currentUser.id,
timezone: currentUser.timezone,
onlyDate,
});
});
}
Form:
<EditCalendarEventForm
show={this.state.editShow}
isSubmitting={editEventProcess.isSubmitting}
calendarEvent={this.state.calendarEvent}
onSubmit={this.editEvent}
onHide={this.hideEditEventModal}
/>
Here is the whole page, maybe you will understand the situation better with it. Also I have an EditFormPage and api to work with the requests.
I was using moveEvent as an example to create editEvent method.
import React, { PropTypes } from 'react';
import moment from 'moment-timezone';
import Helmet from 'react-helmet';
import _ from 'lodash';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { GoogleLogin, GoogleLogout } from 'react-google-login';
import { reduxForm, reset } from 'redux-form';
import BigCalendar from 'react-big-calendar';
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
import 'react-big-calendar/lib/less/styles.less';
import 'react-big-calendar/lib/addons/dragAndDrop/styles.less';
import AddCalendarEventForm from '../../../app/components/AddCalendarEventForm';
import EditCalendarEventForm from '../../../app/components/EditCalendarEventForm';
import { translate } from '../../../common/utilities/localization';
import {
selectCurrentUser,
selectCurrentGoogleUser,
} from '../../containers/App/selectors';
import {
submitGoogleAuth,
fetchGoogleCalendarEvents,
editGoogleCalendarEvent,
addGoogleCalendarEvent,
} from './actions';
import {
selectGoogleAuth,
selectCalendarEvents,
selectAddEventProcess,
selectEditEventProcess,
} from './selectors';
const formName = 'addCalendarEvent';
const formNameEdit = 'editCalendarEvent';
const DragAndDropCalendar = withDragAndDrop(BigCalendar);
const localizer = BigCalendar.momentLocalizer(moment);
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser(),
currentGoogleUser: selectCurrentGoogleUser(),
googleAuth: selectGoogleAuth(),
calendarEvents: selectCalendarEvents(),
addEventProcess: selectAddEventProcess(),
editEventProcess: selectEditEventProcess(),
});
const mapDispatchToProps = (dispatch) => ({
submitGoogleAuth: (externalUserId, googleToken) => dispatch(submitGoogleAuth(externalUserId, googleToken)),
fetchGoogleCalendarEvents: (data) => dispatch(fetchGoogleCalendarEvents(data)),
editGoogleCalendarEvent: (data) => dispatch(editGoogleCalendarEvent(data)),
addGoogleCalendarEvent: (data) => dispatch(addGoogleCalendarEvent(data)),
resetForm: () => dispatch(reset(formName)),
resetEditForm: () => dispatch(reset(formNameEdit)),
});
#reduxForm({
form: formName,
})
#connect(mapStateToProps, mapDispatchToProps)
export default class CalendarPage extends React.Component {
static propTypes = {
currentUser: PropTypes.any,
currentGoogleUser: PropTypes.any,
submitGoogleAuth: PropTypes.func.isRequired,
googleAuth: PropTypes.object,
fetchGoogleCalendarEvents: PropTypes.func,
calendarEvents: PropTypes.object,
editGoogleCalendarEvent: PropTypes.func,
addGoogleCalendarEvent: PropTypes.func,
addEventProcess: PropTypes.object,
editEventProcess: PropTypes.object,
resetForm: PropTypes.func,
resetEditForm: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
events: [],
show: null,
calendarEvent: null,
editShow: null,
};
this.onSuccess = this.onSuccess.bind(this);
this.onFailure = this.onFailure.bind(this);
this.moveEvent = this.moveEvent.bind(this);
this.editEvent = this.editEvent.bind(this);
this.newEvent = this.newEvent.bind(this);
this.showEventModal = this.showEventModal.bind(this);
this.showEditEventModal = this.showEditEventModal.bind(this);
this.hideEventModal = this.hideEventModal.bind(this);
this.hideEditEventModal = this.hideEditEventModal.bind(this);
}
componentDidMount() {
const { currentUser, currentGoogleUser } = this.props;
if (currentGoogleUser && currentGoogleUser.expires_at && moment(currentGoogleUser.expires_at).isAfter(moment())) {
this.props.fetchGoogleCalendarEvents({ ...currentGoogleUser, userId: currentUser.id });
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.currentGoogleUser !== this.props.currentGoogleUser) {
this.props.fetchGoogleCalendarEvents({ ...nextProps.currentGoogleUser, userId: nextProps.currentUser.id });
}
if (nextProps.calendarEvents && nextProps.calendarEvents.details) {
const events = [];
for (const item of nextProps.calendarEvents.details.items) {
if (item.start && item.end) {
events.push({
id: item.id,
title: item.summary,
start: moment(item.start.dateTime || item.start.date),
end: moment(item.end.dateTime || item.end.date),
});
}
}
this.setState({ events });
}
if (!nextProps.addEventProcess.isSubmitting && this.props.addEventProcess.isSubmitting) {
this.hideEventModal();
}
if (!nextProps.editEventProcess.isSubmitting && this.props.editEventProcess.isSubmitting) {
this.hideEventModal();
}
}
onSuccess(ev) {
const { submitGoogleAuth, currentUser } = this.props;
submitGoogleAuth(currentUser.id, { ...ev.tokenObj, profileEmail: ev.profileObj.email });
}
onFailure(ev) {
console.log('onFailure', ev);
}
moveEvent({ event, start, end, isAllDay: droppedOnAllDaySlot }) {
const { currentUser, editGoogleCalendarEvent, calendarEvents } = this.props;
const { events } = this.state;
let onlyDate = false;
const idx = events.indexOf(event);
const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
let allDay = event.allDay;
if (!event.allDay && droppedOnAllDaySlot) {
allDay = true;
} else if (event.allDay && !droppedOnAllDaySlot) {
allDay = false;
}
const updatedEvent = { ...event, start, end, allDay };
const nextEvents = [...events];
nextEvents.splice(idx, 1, updatedEvent);
if (eventIndex !== -1) {
const item = calendarEvents.details.items[eventIndex];
if (item.start.date && item.end.date) {
updatedEvent.start = moment(start).format('YYYY-MM-DD');
updatedEvent.end = moment(end).format('YYYY-MM-DD');
onlyDate = true;
}
}
this.setState({
events: nextEvents,
}, () => {
editGoogleCalendarEvent({ ...updatedEvent, userId: currentUser.id, timezone: currentUser.timezone, onlyDate });
});
}
editEvent(event) {
const { currentUser, editGoogleCalendarEvent, calendarEvents } = this.props;
const { events } = this.state;
let onlyDate = false;
console.log('event', event);
console.log('calendarEvents', calendarEvents);
const idx = events.indexOf(event);
const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
const editedEvent = { ...event };
console.log('Event Index', eventIndex);
const nextEvents = [...events];
nextEvents.splice(idx, 1, editedEvent);
if (eventIndex !== -1) {
const item = calendarEvents.details.items[eventIndex];
if (item.start.date && item.end.date) {
editedEvent.start = moment(event.start).format('YYYY-MM-DD');
editedEvent.end = moment(event.end).format('YYYY-MM-DD');
onlyDate = true;
}
}
this.setState({
events: nextEvents,
}, () => {
console.log('Object', { id: event.event.id, title: event.formValues.title, userId: currentUser.id, timezone: currentUser.timezone, onlyDate });
editGoogleCalendarEvent({
id: event.event.id,
start: moment(event.event.start).hours(event.formValues.period === 'AM' ? event.formValues.hour % 12 : (event.formValues.hour % 12) + 12).minutes(event.formValues.minute).toISOString(),
end: moment(event.event.end).hours(event.formValues.period === 'AM' ? event.formValues.hour % 12 : (event.formValues.hour % 12) + 12).minutes(event.formValues.minute).toISOString(),
title: event.formValues.title,
userId: currentUser.id,
timezone: currentUser.timezone,
onlyDate,
});
});
}
resizeEvent = ({ event, start, end }) => {
const { events } = this.state;
const nextEvents = events.map(existingEvent => {
return existingEvent.id === event.id
? { ...existingEvent, start, end }
: existingEvent;
});
this.setState({
events: nextEvents,
});
// console.log(`${event.title} was resized to ${start}-${end}`);
}
newEvent(params) {
const { currentUser, addGoogleCalendarEvent } = this.props;
const { event, formValues } = params;
const newEvent = {
title: formValues.title,
description: formValues.description ? formValues.description : null,
allDay: event.slots.length === 1,
start: moment(event.start).hours(formValues.period === 'AM' ? formValues.hour % 12 : (formValues.hour % 12) + 12).minutes(formValues.minute).toISOString(),
end: moment(event.end).hours(formValues.period === 'AM' ? formValues.hour % 12 : (formValues.hour % 12) + 12).minutes(formValues.minute).toISOString(),
};
this.setState({
calendarEvent: null,
}, () => {
addGoogleCalendarEvent({ ...newEvent, userId: currentUser.id, timezone: currentUser.timezone });
});
}
showEventModal(event) {
this.setState({ calendarEvent: event, show: true });
}
showEditEventModal(event) {
const { calendarEvents } = this.props;
const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
const item = calendarEvents.details.items[eventIndex];
this.setState({ calendarEvent: item, editShow: true });
}
hideEventModal() {
const { resetForm } = this.props;
this.setState({ show: false, calendarEvent: null }, () => {
resetForm();
});
}
hideEditEventModal() {
const { resetEditForm } = this.props;
this.setState({ editShow: false, calendarEvent: null }, () => {
resetEditForm();
});
}
render() {
const { currentGoogleUser, addEventProcess, editEventProcess } = this.props;
let authorized = false;
if (currentGoogleUser && currentGoogleUser.expires_at) {
authorized = moment(currentGoogleUser.expires_at).isAfter(moment());
}
return (
<div>
<div className="container-fluid">
<Helmet title={translate('portals.page.calendarPage.helmetTitle')} />
<section className="calendar-section">
<h2 className="main-heading">{translate('portals.page.calendarPage.pageTitle')}</h2>
{!authorized &&
<GoogleLogin
clientId={GOOGLE_CLIENT_ID}
scope="https://www.googleapis.com/auth/calendar"
className="google-login"
onSuccess={this.onSuccess}
onFailure={this.onFailure}
>
<i className="google-image" />
<span> Sign in with Google</span>
</GoogleLogin>
}
{authorized &&
<DragAndDropCalendar
selectable
events={this.state.events}
localizer={localizer}
onEventDrop={this.moveEvent}
resizable
onEventResize={this.resizeEvent}
onSelectSlot={this.showEventModal}
onSelectEvent={this.showEditEventModal}
defaultView={BigCalendar.Views.MONTH}
defaultDate={new Date()}
views={{ month: true }}
/>
}
<AddCalendarEventForm
show={this.state.show}
isSubmitting={addEventProcess.isSubmitting}
calendarEvent={this.state.calendarEvent}
onSubmit={this.newEvent}
onHide={this.hideEventModal}
/>
<EditCalendarEventForm
show={this.state.editShow}
isSubmitting={editEventProcess.isSubmitting}
calendarEvent={this.state.calendarEvent}
onSubmit={this.editEvent}
onHide={this.hideEditEventModal}
/>
</section>
</div>
</div>
);
}
}

const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
Apparently, there is an array which is called _, I can't see it anywhere in your code, you should change the way your are doing the findIndex() method.
suchlike calendarEvents.details.items.findIndex({id: event.id})
although, I don't think there is a way to find a prop value inside an object this way.
you may want to loop on instead.

Related

React.js error: The service worker navigation preload request was cancelled before 'preloadResponse' settled

I have an issue with my React application (with Redux Saga), I'm getting the console error:
The service worker navigation preload request was cancelled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle.
I see this error on the console only on Chrome, not in Firefox or Edge.
This error does not affect my application.
The following steps reproduce the error:
1. Main page upload.
2. Go to movie details page.
3. Go back to main page.
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { mainActions } from '../../store/actions/actions';
import './Main.scss';
import { MoviesList, SearchPanel } from '../../components';
const propTypes = {};
const defaultProps = {};
class Main extends Component {
constructor(props) {
super(props);
this.handleSearchTextChange = this.handleSearchTextChange.bind(this);
this.handleLoadMoreButtonClick = this.handleLoadMoreButtonClick.bind(this);
this.handleMovieClick = this.handleMovieClick.bind(this);
this.handleFavoriteMovieClick = this.handleFavoriteMovieClick.bind(this);
}
componentDidMount() {
this.handleComponentDidMount();
}
handleComponentDidMount() {
const { moviesList } = this.props;
if (!moviesList || moviesList.length <= 0) {
this.getMovies(null, false);
}
}
handleLoadMoreButtonClick() {
this.getMovies(null, false);
}
handleMovieClick(e) {
if (e.target.className === 'movie') {
this.props.history.push(`/details/${e.currentTarget.dataset.id}`);
}
}
handleSearchTextChange(e) {
const { pageNumber, favoriteMoviesList } = this.props;
this.props.onSearchTextChange({
searchText: e.target.value,
pageNumber: pageNumber,
favoriteMoviesList: favoriteMoviesList
});
}
handleFavoriteMovieClick(e) {
const { id, name, posterId } = e.currentTarget.dataset;
const { moviesList, favoriteMoviesList } = this.props;
this.props.onUpdateFavoriteMovies({
updatedMovie: { id: id, name: name, posterId: posterId },
favoriteMoviesList: favoriteMoviesList,
moviesList: moviesList
});
}
getMovies(updatedSearchText, isSearchChange) {
const { searchText, pageNumber, favoriteMoviesList } = this.props;
this.props.onLoadMovies({
pageNumber: pageNumber,
favoriteMoviesList: favoriteMoviesList,
updatedSearchText: isSearchChange ? updatedSearchText : searchText,
isSearchChange: isSearchChange
});
}
render() {
const { searchText, isLoadingMoreMovies, isPager, moviesList } = this.props;
return (
<div className="main-area">
<SearchPanel
searchText={searchText}
onSearchTextChange={this.handleSearchTextChange}
/>
<MoviesList
pageName='movies'
moviesList={moviesList}
isLoadingMoreMovies={isLoadingMoreMovies}
isPager={isPager}
onLoadMoreClick={this.handleLoadMoreButtonClick}
onMovieClick={this.handleMovieClick}
onFavoriteMovieClick={this.handleFavoriteMovieClick}
/>
</div>
);
}
}
Main.propTypes = propTypes;
Main.defaultProps = defaultProps;
const mapStateToProps = (state) => {
return {
searchText: state.main.searchText,
pageNumber: state.main.pageNumber,
isLoadingMoreMovies: state.main.isLoadingMoreMovies,
isPager: state.main.isPager,
moviesList: state.main.moviesList,
favoriteMoviesList: state.main.favoriteMoviesList
};
};
const mapDispatchToProps = (dispatch) => {
return {
onLoadMovies: (request) => dispatch(mainActions.loadMovies(request)),
onSearchTextChange: (request) => dispatch(mainActions.searchTextChange(request)),
onUpdateFavoriteMovies: (request) => dispatch(mainActions.updateFavoriteMovies(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Details.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { detailsActions, mainActions } from '../../store/actions/actions';
import './Details.scss';
import { ActorsList, ButtonClick, CrewsList, FeaturesList, PageTitle, ProductionsList, Rating, Trailer } from '../../components';
import movieUtils from '../../utils/movie.utils';
const propTypes = {};
const defaultProps = {};
class Details extends Component {
constructor(props) {
super(props);
this.handleBackClick = this.handleBackClick.bind(this);
this.handleFavoriteMovieClick = this.handleFavoriteMovieClick.bind(this);
this.isFavorite = false;
}
componentDidMount() {
this.handleComponentDidMount();
}
handleComponentDidMount() {
if (this.props.moviesList.length <= 0) {
this.handleBackClick();
return;
}
const movieId = this.props.match.params.id;
if (!movieId) {
this.handleBackClick();
return;
}
this.props.onLoadMovieDetails(movieId);
this.updateIsFavorite(movieId);
}
numberWithCommas(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
updateIsFavorite(movieId) {
this.isFavorite = this.props.favoriteMoviesList.findIndex(movie => parseInt(movie.id) === parseInt(movieId)) > -1;
}
handleBackClick() {
this.props.history.push(`/`);
}
handleFavoriteMovieClick() {
const { movie, moviesList, favoriteMoviesList } = this.props;
this.props.onUpdateFavoriteMovies({
updatedMovie: { id: movie.id, name: movie.title, posterId: movie.poster_path },
favoriteMoviesList: favoriteMoviesList,
moviesList: moviesList
});
this.updateIsFavorite(movie.id);
}
render() {
const { movie, youtubeKey, credits } = this.props;
if (!movie) {
return null;
}
const { adult, poster_path, budget, genres, homepage, imdb_id, original_language, original_title,
overview, popularity, production_companies, production_countries, release_date, revenue, runtime, spoken_languages,
status, tagline, title, video, vote_average, vote_count } = movie;
const genresText = genres.map(genre => genre.name).join(', ');
const countriesText = production_countries.map(country => country.name).join(', ');
const languagesText = spoken_languages.map(language => language.name).join(', ');
const featuresList = [
{ item: 'Release Date', value: release_date },
{ item: 'Budget', value: `$${this.numberWithCommas(budget)}` },
{ item: 'Revenue', value: `$${this.numberWithCommas(revenue)}` },
{ item: 'Length', value: `${runtime} minutes` },
{ item: 'Popularity', value: popularity },
{ item: 'Original Title', value: original_title },
{ item: 'For Adults', value: adult ? 'Yes' : 'No' },
{ item: 'Original Language', value: original_language },
{ item: 'Spoken Languages', value: languagesText },
{ item: 'Countries', value: countriesText },
{ item: 'Status', value: status },
{ item: 'Is Video', value: video ? 'Yes' : 'No' }
];
const linksList = [];
if (homepage) {
linksList.push({ id: 1, name: 'Homepage', url: homepage });
}
if (imdb_id) {
linksList.push({ id: 2, name: 'IMDB', url: `https://www.imdb.com/title/${imdb_id}` });
}
const actorsList = movieUtils.removeDuplicates(credits ? credits.cast ? credits.cast : null : null, 'name');
const crewsList = movieUtils.removeDuplicates(credits ? credits.crew ? credits.crew : null : null, 'name');
return (
<div>
<section className="details-area">
<PageTitle
pageName='details'
pageTitle='Details'
/>
<ul className="details-content">
<li className="details-left" style={{ backgroundImage: `url('https://image.tmdb.org/t/p/original${poster_path}')` }}></li>
<li className="details-right">
<h2>{title} ({release_date.substr(0, 4)})</h2>
<p className="genres">{genresText}</p>
<p className="description short">{tagline}</p>
<Rating
rating={vote_average}
votesCount={this.numberWithCommas(vote_count)}
/>
<p className="description full">{overview}</p>
<div className="extra">
<FeaturesList
featuresList={featuresList.slice(0, 5)}
linksList={null}
isFavorite={this.isFavorite}
onFavoriteMovieClick={this.handleFavoriteMovieClick}
/>
{youtubeKey && <Trailer
youtubeKey={youtubeKey}
/>}
</div>
</li>
<div className="extra-features">
<FeaturesList
featuresList={featuresList.slice(5, featuresList.length)}
linksList={linksList}
isFavorite={null}
onFavoriteMovieClick={null}
/>
<ProductionsList
productionsList={production_companies}
/>
</div>
</ul>
</section>
<section className="actors-area">
<PageTitle
pageName='actors'
pageTitle='Cast'
/>
<ActorsList
actorsList={actorsList}
/>
</section>
<section className="crew-area">
<PageTitle
pageName='crew'
pageTitle='Crew'
/>
<CrewsList
crewsList={crewsList}
/>
</section>
<ButtonClick
buttonText={'Back'}
buttonTitle={'Back'}
isLoading={false}
onClick={this.handleBackClick}
/>
</div>
);
}
}
Details.propTypes = propTypes;
Details.defaultProps = defaultProps;
const mapStateToProps = (state) => {
return {
movie: state.details.movie,
youtubeKey: state.details.youtubeKey,
credits: state.details.credits,
moviesList: state.main.moviesList,
favoriteMoviesList: state.main.favoriteMoviesList
};
};
const mapDispatchToProps = (dispatch) => {
return {
onLoadMovieDetails: (movieId) => dispatch(detailsActions.loadDetails(movieId)),
onUpdateFavoriteMovies: (request) => dispatch(mainActions.updateFavoriteMovies(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Details);
What I already looked in:
Getting The service worker navigation preload request was cancelled before 'preloadResponse' settled
https://learn.microsoft.com/en-us/answers/questions/108004/getting-the-service-worker-navigation-preload-requ.html
https://support.google.com/mail/thread/4055804?hl=en
https://love2dev.com/pwa/service-worker-preload/
I tried to put this on Details.jsx page, but it didn't work:
self.addEventListener('fetch', event => {
event.respondWith(async function () {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse; // Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response; // Else try the network.
return fetch(event.request);
}());
});
self.addEventListener('activate', event => {
event.waitUntil(async function () {
// Feature-detect
if (self.registration.navigationPreload) { // Enable navigation preloads!
console.log('Enable navigation preloads!');
await self.registration.navigationPreload.enable();
} return;
})();
});
How can I solve this issue? Thanks.
Had same error, even my iframe wasn't loading..whatever video you are using from youtube use nocookie/embed in url. It's working for me.
Try changing https://www.youtube.com/watch?v=i8eBBG46H8A to
https://www.youtube-nocookie.com/embed/i8eBBG46H8A
Hope nocookie & embed helps..!!

React Leaflet Routing Machine: onClick to add Marker after all waypoints are removed fires only after second click

I followed this recommendation from the Leaflet Routing Machine regarding interactions i.e. onClicks.
With my implementation, I'm saving the waypoints in local-storage—saving the latitude and longitude obj I get from the map click, to an array called markers
The event handler has a conditional which separates the click into two outcomes—an adding (to the markers array) or updating it.
Like I said in the title, initial interaction is fine, it's just when I remove any marker and try to add it again is the problem. Also I noticed the markers array is completely empty, and next event fired is an update when clearly it should be an addition:
Here is the relevant code in the Routing Machine:
class Routing extends MapLayer {
static contextType = UserContextDispatch;
constructor(props) {
super(props);
this.state = {
showSpinner: false,
localDispatch: null,
};
this.handleLoader = this.handleLoader.bind(this);
this.handleRemoveWayPoint = this.handleRemoveWayPoint.bind(this);
this.handleSetMarker = this.handleSetMarker.bind(this);
}
handleRemoveWayPoint() {
var waypoints = this.control.getWaypoints();
for (let i = waypoints.length - 1; i >= 0; i--) {
console.log('waypoints[i].latLng !== null ', waypoints[i].latLng !== null);
if (waypoints[i].latLng !== null) {
waypoints[i].latLng = null;
break;
}
}
this.control.setWaypoints(waypoints);
}
createLeafletElement(props) {
const { map } = this.props.leaflet;
if (map && !this.control) {
this.control = L.Routing.control({
collapsible: true,
show: false,
position: 'bottomleft',
lineOptions: {
styles: [{ color: 'chartreuse', opacity: 1, weight: 5 }]
},
waypoints: [null],
createMarker: function(i, wp, nWps) {
if (i === 0) {
return L.marker(wp.latLng, {
icon: startIcon,
draggable: true,
keyboard: true,
alt: 'current location'
}).on('drag', function(e) {
e.latlng.alt = 'current location';
console.log('there be dragons start!!', e);
RoutingMachineRef.handleSetMarker({
...e.oldLatLng,
...e.latlng
});
});
}
if (i === nWps - 1) {
return L.marker(wp.latLng, {
icon: endIcon,
draggable: true,
alt: 'current destination'
}).on('drag', function(e) {
e.latlng.alt = 'current destination';
console.log('there be dragons dest!!', e);
RoutingMachineRef.handleSetMarker({
...e.oldLatLng,
...e.latlng
});
});
}
}
});
L.Routing.errorControl(this.control).addTo(map);
}
return this.control.getPlan();
}
componentDidMount() {
const { map } = this.props.leaflet;
console.log('markers ', markers);
this.setState(prevState => {
localDispatch: prevState.localDispatch = this.context.dispatch;
});
map.addControl(this.control);
}
updateLeafletElement(fromProps, toProps) {
const { map } = this.props.leaflet;
var self = this;
self;
var { markers } = this.props;
function createButton(label, container) {
var btn = L.DomUtil.create('button', '', container);
btn.setAttribute('type', 'button');
btn.innerHTML = label;
return btn;
}
var { localDispatch } = this.state;
var container = L.DomUtil.create('div'),
startBtn = createButton('Start from this location', container),
destBtn = createButton('Go to this location', container);
map.on(
'click',
function(e) {
L.popup()
.setContent(container)
.setLatLng(e.latlng)
.openOn(map);
L.DomEvent.on(startBtn, 'click', function() {
if (e.latlng) {
e.latlng.alt = 'current location';
console.log('adding);
localDispatch({
type: 'addMarker',
payload: {
marker: e.latlng
}
});
}
if (markers.length === 0) {
console.log('updating ');
e.latlng.alt = 'current location';
localDispatch({
type: 'updateMarkers',
payload: {
marker: e.latlng
}
});
}
self.control.spliceWaypoints(0, 1, e.latlng);
map.closePopup();
});
L.DomEvent.on(
destBtn,
'click',
function() {
console.log('e', e);
if (markers[1] === undefined) {
e.latlng.alt = 'current destination';
console.log('e.latlng ', e.latlng);
localDispatch({
type: 'addMarker',
payload: {
marker: e.latlng
}
});
}
if (toProps.markers[1] !== undefined) {
console.log('updating ');
e.latlng.alt = 'current destination';
localDispatch({
type: 'updateMarkers',
payload: {
marker: e.latlng
}
});
}
this.control.spliceWaypoints(1, 1, e.latlng);
map.closePopup();
}.bind(this)
);
}.bind(this)
);
if (toProps.removeRoutingMachine !== false) {
this.control.setWaypoints([]);
}
}
componentWillUnmount() {
this.destroyRouting();
}
destroyRouting() {
const { map } = this.props.leaflet;
if (map) {
map.removeControl(this.control);
}
}
}
export default withLeaflet(Routing);
Thanks in advance!
As you can see I have some code related to the Map (the onClick for the waypoints) in the RoutingMachine itself; after thinking about it I moved it to the Map component as a handlerFunction. And now it works!
import React, { useState, useEffect, useRef } from 'react';
import { Button, Dimmer, Loader } from 'semantic-ui-react';
import L from 'leaflet';
import * as ELG from 'esri-leaflet-geocoder';
import Control from 'react-leaflet-control';
// import MapboxLayer from '../MapboxLayer/MapboxLayer.jsx';
import Routing from '../RoutingMachine/RoutingMachine.jsx';
import { parse, stringify } from 'flatted';
import { userState, userDispatch } from '../Context/UserContext.jsx';
import UIContext from '../Context/UIContext.jsx';
function currentMapViewPropsAreEqual(prevProps, nextProps) {
console.log('prevProps, nextProps ', prevProps, nextProps);
console.log(
'prevProps.currentMapView === nextProps.currentMapView && prevProps.Map === nextProps.Map && prevProps.TileLayer === nextProps.TileLayer ',
prevProps.currentMapView === nextProps.currentMapView &&
prevProps.Map === nextProps.Map &&
prevProps.TileLayer === nextProps.TileLayer
);
return (
prevProps.currentMapView === nextProps.currentMapView &&
prevProps.Map === nextProps.Map &&
prevProps.TileLayer === nextProps.TileLayer
);
}
function MyMap({ currentMapView, Map, TileLayer }) {
console.log('currentMapView; ', currentMapView);
var [animate, setAnimate] = useState(false);
var [userLocation, setUserLocation] = useState(null);
const [myState, setMyState] = useState(null);
var handleWaypointsOnMapRef = useRef(handleWaypointsOnMap);
var mapRef = useRef();
var mapRefForRoutingMachine = useRef();
var { state } = userState();
var { dispatch } = userDispatch();
var {
currentMap,
isRoutingVisible,
removeRoutingMachine,
isLengthOfMarkersLessThanTwo,
markers
} = state;
useEffect(() => {
handleWaypointsOnMapRef.current = handleWaypointsOnMap;
}); // update after each render
useEffect(() => {
var { current = {} } = mapRef;
var { leafletElement: map } = current;
console.log('foo');
console.log('currentMap ', currentMapView);
map.locate({ setView: true });
map.on('locationfound', handleOnLocationFound);
}, []);
useEffect(() => {
var searchControl = new ELG.Geosearch({
useMapBounds: false
});
var { current = {} } = mapRef;
var { leafletElement: map } = current;
console.log('mapRef ', mapRef);
searchControl.addTo(map);
var cb = e => handleWaypointsOnMapRef.current(e); // then use most recent cb value
searchControl.on('results', cb);
if (Object.keys(currentMap).length === 0) {
dispatch({
type: 'setMap',
payload: {
currentMap: stringify(map)
}
});
}
return () => {
searchControl.off('results', cb);
};
}, []);
function handleOnClickClearOneMarkerAtTime() {
dispatch({
type: 'setIsRoutingVisible',
payload: {
isRoutingVisible: false
}
});
mapRefForRoutingMachine.current.handleRemoveWayPoint();
dispatch({
type: 'deleteUserMarkers'
});
}
function handleOnClickClearAllMarkers() {
mapRefForRoutingMachine.current.handleClearWayPoints();
dispatch({
type: 'resetUserMarkers'
});
}
function handleOnClickMarkerClick(e) {
e.originalEvent.view.L.DomEvent.stopPropagation(e);
}
function handleWaypointsOnMap(e) {
var { current = {} } = mapRef;
var { leafletElement: map } = current;
dispatch({
type: 'setIsRoutingVisible',
payload: {
isRoutingVisible: true
}
});
dispatch({
type: 'setRemoveRoutingMachine',
payload: {
removeRoutingMachine: false
}
});
function createButton(label, container) {
var btn = L.DomUtil.create('button', '', container);
btn.setAttribute('type', 'button');
btn.innerHTML = label;
return btn;
}
var container = L.DomUtil.create('div'),
startBtn = createButton('Start from this location', container),
destBtn = createButton('Go to this location', container);
L.popup()
.setContent(container)
.setLatLng(e.latlng)
.openOn(map);
L.DomEvent.on(startBtn, 'click', function() {
if (markers.length === 0) {
e.latlng.alt = 'current location';
console.log('adding current location', e.latlng);
dispatch({
type: 'addMarker',
payload: {
marker: e.latlng
}
});
}
if (markers[0] != undefined) {
e.latlng.alt = 'current location';
console.log('updating current location', e.latlng);
dispatch({
type: 'updateMarkers',
payload: {
marker: e.latlng
}
});
}
mapRefForRoutingMachine.current.handleSpliceWaypoints(0, 1, e.latlng);
map.closePopup();
});
L.DomEvent.on(
destBtn,
'click',
function() {
console.log('e', e);
if (markers.length === 1) {
e.latlng.alt = 'current destination';
console.log('adding destination ', e.latlng);
dispatch({
type: 'addMarker',
payload: {
marker: e.latlng
}
});
}
if (markers.length === 2 && markers[1] !== undefined) {
e.latlng.alt = 'current destination';
console.log('updating destination', e.latlng);
dispatch({
type: 'updateMarkers',
payload: {
marker: e.latlng
}
});
}
mapRefForRoutingMachine.current.handleSpliceWaypoints(1, 1, e.latlng);
map.closePopup();
}.bind(this)
);
}
function handleOnViewportChanged(e) {
console.log('viewport change', e);
console.log('currentMapView ', currentMapView);
var { current = {} } = mapRef;
var { leafletElement: map } = current;
map.on('zoomend', function() {
var zoom = map.getZoom();
console.log('zoom ', zoom);
console.log("'dispatch setMapZoom'; ");
dispatch({
type: 'setMapZoom',
payload: {
currentMapView: zoom
}
});
});
}
function handleOnLocationFound(e) {
console.log('e ', e);
var { current = {} } = mapRef;
var { leafletElement: map } = current;
map.setZoom(currentMapView);
var latlng = e.latlng;
var radius = e.accuracy;
var circle = L.circle(latlng, radius);
circle.addTo(map);
}
return (
<Map
preferCanvas={true}
id="myMap"
animate={animate}
zoom={currentMapView}
ref={mapRef}
onViewportChanged={handleOnViewportChanged}
onClick={e => handleWaypointsOnMap(e)}
>
<TileLayer
url={`https://api.mapbox.com/styles/v1/${process.env.MAPBOX_USERNAME}/${
process.env.MAPBOX_STYLE_ID
}/tiles/256/{z}/{x}/{y}#2x?access_token=${process.env.MAPBOX_ACCESS_TOKEN}`}
attribution='Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox'
/>
<Control position="bottomleft">
<Button onClick={handleOnClickClearOneMarkerAtTime} color="orange" size="small">
delete one marker!
</Button>
</Control>
<Control position="bottomright">
<Button onClick={handleOnClickClearAllMarkers} color="red" size="small">
clear all!
</Button>
</Control>
{mapRef && (
<Routing
isRoutingVisible={isRoutingVisible}
ref={mapRefForRoutingMachine}
markers={markers}
stringify={stringify}
isLengthOfMarkersLessThanTwo={isLengthOfMarkersLessThanTwo}
removeRoutingMachine={removeRoutingMachine}
userLocation={userLocation}
/>
)}
</Map>
);
}
var MemoizedMyMap = React.memo(MyMap, currentMapViewPropsAreEqual);
export default MemoizedMyMap;
And this is the Routing Machine:
import { MapLayer } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet-routing-machine';
import { withLeaflet } from 'react-leaflet';
import UserContextDispatch from '../Context/UserContext.jsx';
import { Dimmer, Loader } from 'semantic-ui-react';
import { isEqual } from 'lodash';
import AwesomeDebouncePromise from 'awesome-debounce-promise';
class Routing extends MapLayer {
static contextType = UserContextDispatch;
constructor(props) {
super(props);
this.state = {
showSpinner: false,
localDispatch: null,
markerIsBeingDragged: false,
currentMarker: {}
};
this.handleLoader = this.handleLoader.bind(this);
this.handleRemoveWayPoint = this.handleRemoveWayPoint.bind(this);
this.handleClearWayPoints = this.handleClearWayPoints.bind(this);
this.handleSpliceWaypoints = this.handleSpliceWaypoints.bind(this);
this.handleSetMarker = this.handleSetMarker.bind(this);
}
handleSetMarker(marker) {
var { markers } = this.props;
if (markers[0] !== undefined && markers[0].alt === 'current location') {
this.setState(prevState => ({
currentMarker: { ...prevState.currentMarker, ...marker }
}));
}
if (markers[1] !== undefined && markers[1].alt === 'current destination') {
this.setState(prevState => ({
currentMarker: { ...prevState.currentMarker, ...marker }
}));
}
console.log('this.state ', this.state);
}
handleSpliceWaypoints(start, end, obj) {
this.control.spliceWaypoints(start, end, obj);
}
handleLoader() {
var { showSpinner } = this.state;
if (this.state.showSpinner === false) {
this.setState(function(prevState) {
return { showSpinner: !prevState.showSpinner };
});
return (
<Dimmer active inverted>
<Loader />
</Dimmer>
);
}
this.setState(function(prevState) {
return { showSpinner: (prevState.showSpinner = true) };
});
}
handleRemoveWayPoint() {
var waypoints = this.control.getWaypoints();
for (let i = waypoints.length - 1; i >= 0; i--) {
console.log('waypoints[i].latLng !== null ', waypoints[i].latLng !== null);
if (waypoints[i].latLng !== null) {
waypoints[i].latLng = null;
break;
}
}
console.log('waypoints ', waypoints);
this.control.setWaypoints(waypoints);
}
handleClearWayPoints() {
this.control.setWaypoints([L.latLng(null, null), L.latLng(null, null)]);
}
componentDidMount() {
const { map } = this.props.leaflet;
var { markers } = this.props;
this.control.setWaypoints([L.latLng(markers[0]), L.latLng(markers[1])]);
this.setState(prevState => {
localDispatch: prevState.localDispatch = this.context.dispatch;
});
map.addControl(this.control);
}
createLeafletElement(props) {
const { map } = this.props.leaflet;
var startIcon = new L.Icon({
iconUrl:
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var endIcon = new L.Icon({
iconUrl:
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
if (map && !this.control) {
var RoutingMachineRef = this;
this.control = L.Routing.control({
collapsible: true,
show: false,
position: 'bottomleft',
lineOptions: {
styles: [{ color: 'chartreuse', opacity: 1, weight: 5 }]
},
waypoints: [null],
createMarker: function(i, wp, nWps) {
if (i === 0) {
return L.marker(wp.latLng, {
icon: startIcon,
draggable: true,
keyboard: true,
alt: 'current location'
}).on('move', function(e) {
e.target._latlng.alt = 'current location';
console.log('e.target._latlng', e.target._latlng);
console.log('there be dragons start!!', e);
RoutingMachineRef.setState(prevState => ({
markerIsBeingDragged: !prevState.markerIsBeingDragged
}));
RoutingMachineRef.handleSetMarker(e.target._latlng);
});
}
if (i === nWps - 1) {
return L.marker(wp.latLng, {
icon: endIcon,
draggable: true,
alt: 'current destination'
}).on('move', function(e) {
e.target._latlng.alt = 'current destination';
console.log(' e.target._latlng', e.target._latlng);
RoutingMachineRef.setState(prevState => ({
markerIsBeingDragged: !prevState.markerIsBeingDragged
}));
console.log('there be dragons dest!!', e);
RoutingMachineRef.handleSetMarker(e.target._latlng);
});
}
}
});
L.Routing.errorControl(this.control).addTo(map);
}
return this.control.getPlan();
}
updateLeafletElement(fromProps, toProps) {
var { currentMarker, localDispatch, markerIsBeingDragged } = this.state;
// console.log('fromProps, toProps ', fromProps, toProps);
if (markerIsBeingDragged && currentMarker.hasOwnProperty('alt')) {
if (isEqual(toProps.markers[0], currentMarker) === false) {
localDispatch({
type: 'updateMarkers',
payload: {
marker: currentMarker
}
});
}
if (isEqual(toProps.markers[1], currentMarker) === false) {
localDispatch({
type: 'updateMarkers',
payload: {
marker: currentMarker
}
});
}
this.setState(prevState => ({
markerIsBeingDragged: !prevState.markerIsBeingDragged
}));
}
if (toProps.removeRoutingMachine !== false) {
this.control.setWaypoints([]);
}
}
componentWillUnmount() {
console.log("'unmount' ", 'unmount');
this.destroyRouting();
}
destroyRouting() {
const { map } = this.props.leaflet;
if (map) {
map.removeControl(this.control);
}
}
}
export default withLeaflet(Routing);

Reducer can not change return result

I have 3 tabs as below:
ALL|Top100|Spam
At first load page, ALL is called and I receive 257 return records, then I click tab "Spam" and I receive 11 return records. All is ok.
But when my current page is "Spam" then I click tab "ALL", I also receive 11 return records. It wrong! The result is must 257 records.
After debug, I found "componentWillReceiveProps" in this case is called one time only, while at first load page it called 2 times. Lead to the state on Reducer is not update.
Please help me.
Thank you all
Here my code:
PostPage
/**
* Created by admin on 7/7/17.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import * as combineActions from '../../actions/index';
import { bindActionCreators } from 'redux';
import PostFilter from './PostFilter';
import PaginationView from '../common/PaginationView';
import { MODAL_TYPES } from '../../const';
import List from '../common/List';
import { I18n } from 'react-redux-i18n';
import toastr from 'toastr';
import moment from 'moment';
// import { showModal } from '../../actions/modalAction';
import PostForm from './PostForm';
const Translate = require('react-redux-i18n').Translate;
class PostPage extends Component {
constructor(props, context) {
super(props, context);
this.state = {
currentPage: props.paging.currentPage,
displayMode: props.displayMode,
searchKey: '',
fromDate: '',
toDate: '',
flgRng: '',
category: '',
};
}
handleChangeDate = (dateName, dateValue) => {
let fromDate, toDate;
if (dateName === 'fromDate') {
fromDate = dateValue;
this.setState({
fromDate,
})
}else{
toDate = dateValue;
this.setState({
toDate,
})
}
}
handlelChangeFilter = (event) => {
this.setState({
[event.target.name]: event.target.value
});
}
handleSearch = () => {
const { searchKey, category } = this.state;
let { flgRng, fromDate, toDate } = this.state;
const params = {};
if(this.validate(fromDate, toDate) ===false)
{
return;
}
if (category) {
params['category.id'] = category;
}
let fD, tD;
if(fromDate){
fD = this.formatDate(fromDate);
flgRng = "1";
}else{
flgRng = "";
}
if(toDate){
tD = this.formatDate(toDate);
}
this.setState({
flgRng
})
this.props.actions.fetchPosts({ SearchKey: searchKey, FromDate: fD, ToDate: tD, FlgRng: flgRng, Query: JSON.stringify(params), Mode: this.props.displayMode})
}
handleShowFormNew = () => {
this.props.actions.showModal(
MODAL_TYPES.POST_NEW,
Object.assign(
{
title: I18n.t('common.delete_confirm'),
message: <PostForm />,
type: 'delete_confirm',
handleOK: () => {},
},
this.props,
),
);
}
validate = (fromDate, toDate) => {
if(!fromDate && toDate){
toastr.error(I18n.t('post.msg_fdate'));
return false;
}
if(fromDate && (fromDate === null || fromDate === "" || Date.parse(fromDate) === NaN)){
toastr.error(I18n.t('post.msg_format_fdate'));
return false;
}
if(toDate && (toDate === null || toDate === "" || Date.parse(toDate) === NaN)){
toastr.error(I18n.t('post.msg_format_tdate'));
return false;
}
if(fromDate && toDate){
fromDate = new Date(fromDate)
toDate = new Date(toDate)
if(fromDate > toDate){
toastr.error(I18n.t('post.msg_compare_fdtd'));
return false;
}
}
}
formatDate = (date) => {
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2) month = '0' + month;
if (day.length < 2) day = '0' + day;
return [month, day, year].join('-');
}
componentWillMount() {
this.props.actions.setCurrentPage(1);
this.props.actions.fetchPosts();
this.props.actions.fetchCategories();
}
deletePost = (item) => {
this.props.actions.showModal(MODAL_TYPES.CONFIRM_MODAL, Object.assign({
title: I18n.t('common.delete_confirm'),
message: (<Translate value="members.delete_confirm_msg" name={item.content} dangerousHTML />),
type: 'delete_confirm',
handleOK: () => {
this.props.actions.deletePost(item._id)
.then(rs => {
this.props.actions.hideModal();
this.props.actions.triggerRefresh();
toastr.success(I18n.t('post.post_deleted_success'));
}).catch(error => {
this.props.actions.hideModal();
toastr.error(error.message || error);
});
}
}, this.props));
}
componentWillReceiveProps(nextProps) {
const triggerRefresh = this.props.triggerRefresh;
let params = {};
if((this.state.displayMode !== nextProps.displayMode)||(this.state.currentPage !== nextProps.paging.currentPage || triggerRefresh !== nextProps.triggerRefresh)){
this.setState({ currentPage: nextProps.paging.currentPage, displayMode: nextProps.displayMode, searchKey: this.state.searchKey, fromDate: this.state.fromDate, toDate: this.state.toDate, flgRng: this.state.flgRng, category: this.state.category});
if(this.state.category){
params['category.id'] = this.state.category;
}
let fromDate, toDate, flgRng;
if(this.validate(this.state.fromDate, this.state.toDate) ===false)
{
return;
}
if(this.state.fromDate){
fromDate = this.state.fromDate.format("MM-DD-YYYY");
flgRng = "1"
}
if(this.state.toDate){
toDate = this.state.toDate.format("MM-DD-YYYY");
}
this.props.actions.fetchPosts({PageNumber: nextProps.paging.currentPage , SearchKey: this.state.searchKey, FromDate: fromDate, ToDate: toDate, FlgRng: flgRng, Query: JSON.stringify(params), Mode: nextProps.displayMode})
}
}
render() {
const {posts, actions, triggerRefresh, categories, displayMode} = this.props;
const {currentPage} = this.state;
let data = []
let listOptions = {}
if(posts.items.length > 0 && posts.items[0].spamRecs && displayMode ==='spam'){
data = posts.items.length > 0 ? posts.items.map(key => {
key.name = key.author? `${key.author.firstName} ${key.author.lastName}`: '';
key.categoryName = key.category ? key.category.name : '';
key.createdDate = moment(key._created).format('MM/DD/YYYY');
key.reportedDate = key.spamRecs ? moment(key.spamRecs['_created']).format('MM/DD/YYYY') : "";
key.numberReport = key.spamRecs ? key.spamRecs.length : 0
return key;
}): []
listOptions = {
headers: [I18n.t('common.title'), I18n.t('post.category'), I18n.t('post.author'), I18n.t('post.created_date_time'), I18n.t('post.number_report'), I18n.t('post.reported_date'), I18n.t('common.action')],
data: {
items: data,
mapping: ['content', 'categoryName','name', 'createdDate', 'numberReport', 'reportedDate', 'action'],
align:["","","","","center","",""],
render: [
{
key: "content",
render: (item) => {
return <div className="overflow" style={{ width: '180px'}}>{item.content}</div>
}
},
{
key: "categoryName",
render: (item) => {
return <div className="overflow" style={{ width: '140px'}}>{item.categoryName}</div>
}
},
{
key: "name",
render: (item) => {
return <div className="overflow" style={{ width: '60px'}}>{item.name}</div>
}
}
],
},
params: {url: '/posts/detail/%s/info' + '/' +this.state.displayMode, deleteAction: this.deletePost},
count: posts.count
};
}else{
data = posts.items.length > 0 ? posts.items.map(key => {
key.name = key.author? `${key.author.firstName} ${key.author.lastName}`: '';
key.numberComment = key.comments ? key.comments.length: 0;
key.numberLike = key.likes ? key.likes.length : 0;
key.categoryName = key.category ? key.category.name : '';
key.createdDate = moment(key._created).format('MM/DD/YYYY');
return key;
}): []
listOptions = {
headers: [I18n.t('common.title'), I18n.t('post.category'), I18n.t('post.author'), I18n.t('post.created_date_time'), I18n.t('post.number_comment'), I18n.t('post.number_view'), I18n.t('post.number_like'), I18n.t('common.action')],
data: {
items: data,
mapping: ['content', 'categoryName','name', 'createdDate', 'numberComment', 'views', 'numberLike', 'action'],
align:["","","","","center","center","center",""],
render: [
{
key: "content",
render: (item) => {
return <div className="overflow" style={{ width: '180px'}}>{item.content}</div>
}
},
{
key: "categoryName",
render: (item) => {
return <div className="overflow" style={{ width: '140px'}}>{item.categoryName}</div>
}
},
{
key: "name",
render: (item) => {
return <div className="overflow" style={{ width: '60px'}}>{item.name}</div>
}
}
],
},
params: {url: '/posts/detail/%s/info' + '/' +this.state.displayMode, deleteAction: this.deletePost},
count: posts.count
};
}
return (
<div className="table-page">
<h4 className="module-title">{I18n.t('post.page.title')}</h4>
<PostFilter
categories={categories}
searchKey={this.state.searchKey}
fromDate={this.state.fromDate}
toDate={this.state.toDate}
onSearch={this.handleSearch}
onShow={this.handleShowFormNew}
onChangeDate = {this.handleChangeDate}
onChangeFilter = {this.handlelChangeFilter}
/>
<List {...listOptions} />
<PaginationView count={posts.count} currentPage={currentPage} onSelectPage={(page) => {actions.setCurrentPage(page)}}/>
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
posts: state.posts.page,
categories: state.posts.categories,
paging: state.paging,
i18n: state.i18n,
triggerRefresh: state.posts.triggerRefresh
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(combineActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PostPage);
postsReducer
/**
* Created by admin on 7/12/17.
*/
import {POSTS} from '../const/actionTypes';
const initialState = {
page: {
items: [],
count: 0
},
detail: {},
post: {
items: [],
count: 0
},
comment: {
items: [],
count: 0
},
triggerRefresh: false,
categories: [],
currentP: {},
pListComment: [],
pList: [],
pListReporter: [],
};
export default function posts(state = initialState, action) {
switch(action.type) {
case POSTS.FETCH_SUCCESS:
return {...state, page: {items: action.payload.items, count: action.payload.count}};
case POSTS.FETCH_CATEGORY_SUCCESS:
return {...state, categories: action.payload};
case POSTS.TRIGGER_REFRESH:
return {...state, triggerRefresh: !state.triggerRefresh}
case POSTS.GET_POST_DETAIL_SUCCESS:
return { ...state, currentP: Object.assign({}, action.payload) };
case POSTS.CREATE_SUCCESS:
return { ...state, pList: [action.payload, ...state.pList], currentP: {} };
case POSTS.UPDATE_SUCCESS:
let updated = action.payload;
let newItems = state.pList.map(p => {
return p._id === updated._id ? Object.assign({}, p, updated) : p;
});
return { ...state, pList: newItems, currentPS: action.payload };
case POSTS.DELETE_POSTS_SUCCESS:
return { ...state, pList: state.pList.filter(item => item._id != action.payload) };
case POSTS.GET_POST_COMMENT_SUCCESS:
return { ...state, pListComment: action.payload };
case POSTS.GET_POST_REPORTER_SUCCESS:
return { ...state, pListReporter: action.payload };
default:
return state;
}
}

jest test case for input change and button in reactjs

I am very new to jest so i don't know how to proceed with jest. I have write test case using jest for input on change and button click for the below component. But it failed. issue with 'Method “props” is only meant to be run on a single node. 0 found instead.' please help me
code:
import React from 'react';
import { Col } from 'react-bootstrap';
class FITB extends React.Component {
constructor(props) {
super(props);
this.String = String;
this.defaultTextSize = 8;
this.state = {
inputList: [],
splitList: [],
count: 0,
isValid: false
};
this.onInputChange = this.onInputChange.bind(this);
this.onClickSend = this.onClickSend.bind(this);
this.checkInputValidations = this.checkInputValidations.bind(this);
}
componentDidMount() {
this.initialize();
}
onInputChange(index) {
return (event) => {
const inputList = this.state.inputList;
let isValid = true;
inputList[index] = event.target.value;
if (!this.isValidInput(inputList[index])) {
isValid = false;
}
this.setState({
inputList,
isValid
});
// console.log('onInputChange fib state', this.state);
};
}
onClickSend() {
const {
splitList,
inputList,
count
} = this.state;
// console.log('onClickSend fib before state', this.state);
const onReply = this.props.onReply;
let fullText = '';
splitList.map((text, index) => {
fullText += text;
if ((index < count - 1) && inputList[index]) {
fullText += inputList[index];
}
return true;
});
if (onReply) {
onReply(fullText);
}
// console.log('onClickSend fib after state', this.state);
}
isValidInput(text) {
const regex = /^[\u0020-\u007e]*$/;
const replaceChar160RegExp = new RegExp(this.String.fromCharCode(160), 'g');
return regex.test(text.replace(replaceChar160RegExp, ' '));
}
initialize() {
let text = '';
this.props.messages.map((element) => {
if (element.type && (typeof element.type === 'string') && (element.type === 'FILL_IN_THE_BLANK')) {
text = element.message;
}
// console.log('inside fib', text);
return text;
});
const splitList = text.split(/_+/g);
this.setState({
splitList,
count: splitList.length
});
// console.log('init fib state', this.state);
}
checkInputValidations() {
const {
inputList,
count,
isValid
} = this.state;
let i;
let flag = false;
for (i = 0; i < count - 1; i += 1) {
if (!inputList[i] || inputList[i].trim() === '') {
flag = true;
}
}
// console.log('checkInputValidations', this.state);
return flag || !isValid;
}
render() {
const {
splitList,
count,
inputList
} = this.state;
// console.log('reder fitb', this.state);
return (
<Col lg={12} className="rply-block">
<Col lg={11} className="textarea-block">
<div className="fitb-wrap">
{ splitList && splitList.map((item, index) => (
<span>
<span className="fitb-text">{item}</span>
{ (index < count - 1) && (
<input
className="fitb-input"
type="text"
maxLength="40"
size={(inputList[index] && inputList[index].length > this.defaultTextSize && inputList[index].length) || this.defaultTextSize}
value={inputList[index]}
onChange={this.onInputChange(index)}
autoFocus={index === 0}
aria-describedby={count > 1 ? `Missing word ${index + 1} of ${count - 1}` : 'Missing word'}
aria-label="Fill in missing words"
/>
)}
</span>
))}
</div>
</Col>
<Col lg={1} className="">
<button
className="btn-info-dm"
role="button"
tabIndex="0"
onClick={this.onClickSend}
disabled={this.checkInputValidations()}
>Send</button>
</Col>
</Col>
);
}
}
FITB.propTypes = {
onReply: React.PropTypes.isRequired,
messages: React.PropTypes.array
};
export default FITB;
test files for the input and button click
import React from 'react';
import FITB from '../components/dialogManager/fitb';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
describe('FITB', () => {
let component;
const mockFn = jest.fn();
beforeEach(() => {
component = shallow(<FITB onReply={mockFn} />);
});
test('Should initialize the FITB content', () => {
expect(component.find('.rply-block')).toHaveLength(1);
});
test('Should have been called send', () => {
component.find('.btn-info-dm').simulate('click');
expect(mockFn).toHaveBeenCalled();
});
test('Should render the text box', () => {
expect(component.state().inputList).toEqual([]);
expect(component.state().isValid).toEqual(false);
// expect(component.find('input.fitb-input')).toBeDefined();
console.log('state== ', component.state());
console.log('input == ', component.find('.fitb-input'));
component.find('.fitb-input').simulate('change', { target: { value: 'Qualitative data includes detailed interviews, direct _____, and historical records.' } });
console.log('fitb-input onchange== ', component.find('.fitb-input'));
expect(component.state().inputList).toEqual('Qualitative data includes detailed interviews, direct _____, and historical records.');
expect(component.state().isValid).toEqual(true);
});
// test('Should check the input label', () => {
// const expectedFitbTextLabel = 'Fill in missing words';
// const fitbTextList = [];
// console.log('span fitb txt== ', component.find('.fitb-text').instance().label);
// component.find('.fitb-text').map((elem) => {
// fitbTextList.push(elem.text().trim());
// });
// expect(component.find('.fitb-text').instance().label).toEqual(expectedFitbTextLabel);
// });
test('Should render the fitbText ', () => {
const expectedFitbText = 'Qualitative data includes detailed interviews, direct _____, and historical records.';
const fitbTextList = [];
console.log('span fitb txt== ', component.find('.fitb-text').text());
// fitbTextList.push(expectedFitbText.split(/_+/g));
// console.log('fitbTextList= ', fitbTextList);
component.find('.fitb-text').map((elem) => {
fitbTextList.push(elem.text().trim());
});
expect(fitbTextList).toEqual(expectedFitbText);
});
test('Should check the fitbText ', () => {
const expectedFitbText = 'Qualitative data includes detailed interviews, direct _____, and historical records.';
const fitbTextList = [];
fitbTextList.push(expectedFitbText.split(/_+/g));
expect(component.state().inputList).toEqual([]);
console.log('input list init== ', component.state().inputList);
component.find('input.fitb-input').simulate('change', { target: { value: 'test' } });
console.log('input list== ', component.state().inputList);
// component.find('input').instance().onInputChange(0);
// expect(component.state().inputList).toEqual('test');
});
});
for the another component on button click to send the details
import React from 'react';
class ReplyButtons extends React.Component {
constructor(props) {
super(props);
this.replyBtnWrap = '';
this.replyBtnList = '';
this.state = {
list: [],
isLeftArrowEnabled: false,
isRightArrowEnabled: false
};
this.onClickLeftArrow = this.onClickLeftArrow.bind(this);
this.onClickRightArrow = this.onClickRightArrow.bind(this);
this.onClickReplyBtn = this.onClickReplyBtn.bind(this);
}
componentDidMount() {
this.initializeList();
}
componentDidUpdate() {
this.checkElementOffset();
}
onClickRightArrow() {
const wrapElement = this.replyBtnWrap;
const listElement = this.replyBtnList;
const wrapWidth = wrapElement.offsetWidth;
const listWidth = listElement.offsetWidth;
let listLeft = wrapElement.scrollLeft;
let listOverflowWidth = 0;
listLeft += 400;
listOverflowWidth = listWidth - listLeft;
if (listOverflowWidth < 0) {
listLeft = listWidth - wrapWidth;
}
wrapElement.scrollLeft = listLeft;
this.checkElementOffset();
}
onClickLeftArrow() {
const wrapElement = this.replyBtnWrap;
let listLeft = wrapElement.scrollLeft;
listLeft -= 400;
if (listLeft < 0) {
listLeft = 0;
}
wrapElement.scrollLeft = listLeft;
this.checkElementOffset();
}
onClickReplyBtn(item) {
return () => {
const onReply = this.props.onReply;
if (onReply) {
onReply(item);
}
};
}
checkElementOffset() {
const wrapElement = this.replyBtnWrap;
const listElement = this.replyBtnList;
const wrapWidth = wrapElement.offsetWidth;
const listWidth = listElement.offsetWidth;
const listLeft = wrapElement.scrollLeft;
let listOverflowWidth = 0;
let isLeftArrowEnabled = false;
let isRightArrowEnabled = false;
if (listLeft > 0) {
isLeftArrowEnabled = true;
}
listOverflowWidth = listWidth - listLeft - wrapWidth;
if (listOverflowWidth > 0) {
isRightArrowEnabled = true;
}
if (this.state.isLeftArrowEnabled !== isLeftArrowEnabled || this.state.isRightArrowEnabled !== isRightArrowEnabled) {
this.setState({
isLeftArrowEnabled,
isRightArrowEnabled
});
}
}
initializeList() {
// this.setState({
// list: [{
// type: 'MENU_ITEM',
// text: 'what is quantitative research?',
// return_value: 'what is quantitative research?'
// }, {
// type: 'MENU_ITEM',
// text: 'what is mixed method research?',
// return_value: 'what is mixed method research?'
// }, {
// type: 'MENU_ITEM',
// text: 'what is qualitative research?',
// return_value: 'what is qualitative research?'
// }, {
// type: 'MENU_ITEM',
// text: 'I had a different question',
// return_value: 'I had a different question'
// }, {
// type: 'MENU_ITEM',
// text: 'That was actually my answer',
// return_value: 'That was actually my answer'
// }]
// });
const replyButtonText = [];
// console.log('reply btns props = ', this.props);
if (this.props.messages) {
this.props.messages.map((element) => {
if (element.type && (typeof element.type === 'string') && (element.type === 'MENU_ITEM')) {
replyButtonText.push(element);
}
return this.setState({ list: replyButtonText });
});
}
}
render() {
const btnList = this.state.list;
const {
isLeftArrowEnabled,
isRightArrowEnabled
} = this.state;
return (
<div className="r-wrap">
{ isLeftArrowEnabled && (
<button className="r-btn-left-arrow" onClick={this.onClickLeftArrow} role="button" tabIndex="0">
<i className="glyphicon glyphicon-menu-left" />
</button>
)}
<div className="r-btn-wrap" ref={(e) => { this.replyBtnWrap = e; }}>
<div className="r-btn-list" ref={(e) => { this.replyBtnList = e; }}>
{
btnList && btnList.map(btnItem => <button
className="r-btn"
role="button"
tabIndex="0"
onClick={this.onClickReplyBtn(btnItem)}
title={btnItem.text}
>{btnItem.text}</button>)
}
</div>
</div>
{ isRightArrowEnabled && (
<button className="r-btn-right-arrow" onClick={this.onClickRightArrow} role="button" tabIndex="0">
<i className="glyphicon glyphicon-menu-right" />
</button>
)}
</div>
);
}
}
ReplyButtons.propTypes = {
onReply: React.PropTypes.isRequired,
messages: React.PropTypes.array
};
export default ReplyButtons;
test file:
import React from 'react';
import ReplyButtons from '../components/dialogManager/replybuttons';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
const mockOutputObj = [{
type: 'MENU_ITEM',
text: 'what is quantitative research?',
return_value: 'what is quantitative research?'
}, {
type: 'MENU_ITEM',
text: 'what is mixed method research?',
return_value: 'what is mixed method research?'
}];
describe('ReplyButtons', () => {
let component;
const mockFn = jest.fn();
beforeEach(() => {
component = shallow(<ReplyButtons onReply={mockFn} />);
});
test('Should initialize the ReplyButtons content', () => {
expect(component.find('.r-wrap')).toHaveLength(1);
});
test('Should check the ReplyButtons click', () => {
console.log('r-btn==> ', component.find('button.r-btn'));
component.find('.r-btn').simulate('click');
expect(mockFn).toHaveBeenCalled();
});
});
Please help me.
I think the error happens because the selector in the last test case
component.find('button.r-btn')
refers to buttons that are build by iterating on this.state.list in the ReplyButtons component
AND you do not provide the prop 'messages' that would trigger a feeding to the list
try instanciating ReplyButtons with some messages like
beforeEach(() => {
component = shallow(<ReplyButtons onReply={mockFn} messages={['1','2']}/>);
});
and it should be ok (btw not having the error stack is pretty unconfortable here, there might be other issues)

Cannot Find what is causing this : Warning: setState(...): Can only update a mounted or mounting component

So after looking over many different questions asking the about this warning, I have found that there is not one single reason why this would be occurring making it difficult to infer a solution on my own code.
So here is my code incase anyone has another pair of eyes they can put on it and spot maybe why i would be getting this error.
This error occurs when not on first arrival of the component but after leaving and returning to it again.
I have a smart container and a dumb component set up so here is the container:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { listOrders, listUseCases } from '../../actions/order';
import { storeWithExpiration } from '../../utils/common.js';
import OrdersIndex from './OrdersIndex';
export class OrdersIndexContainer extends React.Component {
static propTypes = {
account: PropTypes.object.isRequired,
orders: PropTypes.object.isRequired,
platformMap: PropTypes.object.isRequired,
progress: PropTypes.number,
listOrdersAction: PropTypes.func.isRequired,
listUseCasesAction: PropTypes.func.isRequired,
};
render() {
const { orders, platformMap, progress } = this.props;
return (
<div>
<OrdersIndex
orders={ orders }
platformMap={ platformMap }
progress={ progress }
/>
</div>
);
}
renderOrderIndex = () => {
}
componentWillMount = () => {
const { account, listOrdersAction, listUseCasesAction } = this.props;
const token = storeWithExpiration.get('token');
listOrdersAction(token);
listUseCasesAction(account.id, token);
}
}
function mapStateToProps(state) {
const { account, orderList, progress } = state;
const orders = orderList.get('orders');
const platformMap = orderList.get('platformMap');
return { account, platformMap, orders, progress };
}
export default connect(mapStateToProps, {
listOrdersAction: listOrders,
listUseCasesAction: listUseCases,
})(OrdersIndexContainer);
And here is the dumb component:
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import ReactDataGrid from 'react-data-grid';
import { Toolbar } from 'react-data-grid/addons';
import { Data } from 'react-data-grid/addons';
import moment from 'moment';
import { SIMPLE_DATE_FORMAT } from '../../config/app_config';
// import OrderWrapFormatter from './OrderWrapFormatter';
const TABLE_COLUMNS = [
{ key: 'cNumber', name: 'Customer #', width: 125 },
{ key: 'name', name: 'Name', width: 150 },
{ key: 'orderNumber', name: 'Order #', width: 90 },
{ key: 'platform', name: 'Platform' },
{ key: 'useCase', name: 'Use Case'/* , formatter: OrderWrapFormatter */ },
{ key: 'list', name: 'List' },
{ key: 'sku', name: 'SKU' },
{ key: 'startDate', name: 'Start Date' },
{ key: 'endDate', name: 'End Date' },
];
export default class OrdersIndex extends React.Component {
static propTypes = {
orders: PropTypes.object.isRequired,
platformMap: PropTypes.object.isRequired,
progress: PropTypes.number,
};
state = {
rows: [],
originalRows: [],
columns: TABLE_COLUMNS,
sortColumn: null,
sortDirection: null,
filters: {},
}
renderRows = (orders) => {
const _rows = [];
orders.map((o) => {
_rows.push({
key: o.order.id,
id: o.order.id,
cNumber: o.order.providerCustomerNumber,
name: o.order.company.name,
orderNumber: o.order.providerOrderNumber,
platform: o.platformUseCases[0].platform.description,
useCase: this.renderMulti(o.platformUseCases, 'useCase', 'description'),
list: this.renderMulti(o.listSKUs, 'dataSet', 'name'),
sku: this.renderMulti(o.listSKUs, 'fieldSet', 'name'),
startDate: moment(o.order.startDate).format(SIMPLE_DATE_FORMAT),
endDate: moment(o.order.endDate).format(SIMPLE_DATE_FORMAT),
});
return _rows;
});
return this.setState({
rows: _rows,
originalRows: _rows.slice(),
});
}
getRows = () => {
return Data.Selectors.getRows(this.state);
}
rowGetter = (rowIdx) => {
const rows = this.getRows();
return rows[rowIdx];
}
getSize = () => {
return this.getRows().length;
}
renderMulti = (multi, itemName, subItemName) => {
const objectArray = multi.map((object) => {
return object[itemName][subItemName];
});
return objectArray.join('\n');
}
handleGridSort = (sortColumn, sortDirection) => {
const { originalRows, rows } = this.state;
const comparer = (a, b) => {
if (sortDirection === 'ASC') {
return (a[sortColumn] > b[sortColumn]) ? 1 : -1;
}
else if (sortDirection === 'DESC') {
return (a[sortColumn] < b[sortColumn]) ? 1 : -1;
}
};
const newRows = sortDirection === 'NONE' ? originalRows.slice() : rows.sort(comparer);
this.setState({
rows: newRows,
});
}
handleRowUpdated = (e) => {
// merge updated row with current row and rerender by setting state
const { rows } = this.state;
Object.assign(rows[e.rowIdx], e.updated);
this.setState({
...rows,
});
}
handleFilterChange = (filter) => {
const { filters } = this.state;
const newFilters = Object.assign({}, filters);
if (filter.filterTerm) {
newFilters[filter.column.key] = filter;
}
else {
delete newFilters[filter.column.key];
}
this.setState({
filters: newFilters,
});
}
onClearFilters = () => {
// all filters removed
this.setState({
filters: {},
});
}
// Creates appropriate warnings to prevent entering
// the order form if the account is missing information
renderNotice = (message, buttonMessage, route) => {
return (
<div className="alert alert-warning">
<strong>Notice:</strong>
<p>{ message }</p>
<p>
<Link
to={ route }
className="btn btn-warning"
>
<i className='fa fa-plus'></i>
{ buttonMessage }
</Link>
</p>
</div>
);
}
render() {
const { platformMap, progress } = this.props;
const platformMessage = 'Your account is not associated with any platform use cases.' +
'You must select at least one use case before creating new orders.';
const platformButton = 'Add Use Cases';
const platformRoute = '/products';
return (
<div className="container">
<div className="row">
<div className="col-sm-12 col-md-8">
<h1>Orders</h1>
</div>
<div className="col-sm-12 col-md-4">
<span className="pull-right">
<Link
to="/orders/create/1"
className="btn btn-primary"
disabled
>
<i className='fa fa-plus'></i>Create New Order
</Link>
</span>
</div>
</div>
{ platformMap.size === 0 && progress === 0 ?
this.renderNotice(platformMessage, platformButton, platformRoute) : null }
<div className="row">
{ progress === 0 ?
<div className="col-md-12">
{ this.renderTable() }
</div> : null }
</div>
</div>
);
}
renderTable = () => {
const { orders } = this.props;
const { columns } = this.state;
return (
<div>
{ orders.size === 0 || orders === undefined ?
<p>Your account has no orders</p> :
<ReactDataGrid
onGridSort={ this.handleGridSort }
rowKey="key"
id="key"
columns={ columns }
rowGetter={ this.rowGetter }
rowsCount={ this.getSize() }
onRowUpdated={ this.handleRowUpdated }
toolbar={ <Toolbar enableFilter /> }
onAddFilter={ this.handleFilterChange }
onClearFilters={ this.onClearFilters }
minHeight={ 500 }
filterRowsButtonText="Search By Field"
/>
}
</div>
);
}
componentWillMount = () => {
const { orders } = this.props;
const columnArray =
TABLE_COLUMNS.map((c) => {
const copy = Object.assign({}, c);
copy.filterable = true;
copy.locked = true;
if (copy.key !== 'useCase') {
copy.sortable = true;
}
return copy;
});
this.setState({
columns: columnArray,
});
this.renderRows(orders);
}
componentWillReceiveProps = (nextProps) => {
const { orders } = nextProps;
if (orders.size > 0) {
this.renderRows(orders);
}
}
}
I understand this might be a lot but I cannot for the life of me determine what could be the cause. Thanks to anyone who takes a look.

Resources